Compare commits

...

20 commits

Author SHA1 Message Date
KMY(雪あすか)
3c08e48a70
Merge pull request #754 from kmycode/kbtopic-fix-emoji-reaction-rack-attack-for-12
Release: 12.2
2024-06-03 07:46:13 +09:00
KMY
58040c5aa7 Bump version to 12.2 2024-06-02 11:21:08 +09:00
KMY
0bd26af2dd Fix: 絵文字リアクションに厳しいレートリミットが適用される問題 2024-06-02 11:19:00 +09:00
KMY(雪あすか)
652d037440
Merge pull request #750 from kmycode/kb-draft-12.1
Release: 12.1
2024-05-31 08:39:15 +09:00
KMY
7615e12e88 Fix test 2024-05-31 08:11:57 +09:00
KMY
35bd3ea5b7 Fix dicker-compose 2024-05-31 08:08:12 +09:00
KMY
28b74eabac Bump version to 12.1 2024-05-30 23:36:44 +09:00
Claire
d65f8a1196 Bump version to v4.3.0-alpha.4 (#30482) 2024-05-30 23:36:33 +09:00
Claire
2a05566c5c Fix rate-limiting incorrectly triggering a session cookie on most endpoints (#30483) 2024-05-30 23:35:24 +09:00
Claire
b75b26bab4 Merge pull request from GHSA-c2r5-cfqr-c553
* Add hardening monkey-patch to prevent IP spoofing on misconfigured installations

* Remove rack-attack safelist
2024-05-30 23:35:17 +09:00
Claire
993bae2850 Merge pull request from GHSA-q3rg-xx5v-4mxh 2024-05-30 23:35:11 +09:00
Claire
4bfcf0d3f0 Merge pull request from GHSA-5fq7-3p3j-9vrf 2024-05-30 23:35:03 +09:00
Emelia Smith
8e788e260e Fix: remove broken OAuth Application vacuuming & throttle OAuth Application registrations (#30316)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2024-05-30 23:34:50 +09:00
KMY
5ba5aa5c5c Normalize language code of incoming posts (#30403) 2024-05-30 23:33:59 +09:00
Claire
3807dd2352 Fix leaking Elasticsearch connections in Sidekiq processes (#30450) 2024-05-30 23:31:18 +09:00
Claire
2432d870f5 Update dependency rexml to 3.2.8 2024-05-30 23:29:59 +09:00
KMY(雪あすか)
420316fa1a
Merge pull request #732 from kmycode/kb-draft-12.0
Release: 12.0
2024-05-08 06:53:08 +09:00
KMY
1e25b59ed8 Merge branch 'kb_development' into kb-draft-12.0 2024-04-27 09:01:28 +09:00
KMY
86adad74b4 Bump version to 12.0 2024-04-25 18:32:21 +09:00
KMY
78a20bbc2d Bump ruby version to 3.2.4 2024-04-25 18:31:41 +09:00
19 changed files with 381 additions and 114 deletions

View file

@ -2,6 +2,61 @@
All notable changes to this project will be documented in this file.
## [4.2.9] - 2024-05-30
### Security
- Update dependencies
- Fix private mention filtering ([GHSA-5fq7-3p3j-9vrf](https://github.com/mastodon/mastodon/security/advisories/GHSA-5fq7-3p3j-9vrf))
- Fix password change endpoint not being rate-limited ([GHSA-q3rg-xx5v-4mxh](https://github.com/mastodon/mastodon/security/advisories/GHSA-q3rg-xx5v-4mxh))
- Add hardening around rate-limit bypass ([GHSA-c2r5-cfqr-c553](https://github.com/mastodon/mastodon/security/advisories/GHSA-c2r5-cfqr-c553))
### Added
- Add rate-limit on OAuth application registration ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30316))
- Add fallback redirection when getting a webfinger query `WEB_DOMAIN@WEB_DOMAIN` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28592))
- Add `digest` attribute to `Admin::DomainBlock` entity in REST API ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/29092))
### Removed
- Remove superfluous application-level caching in some controllers ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29862))
- Remove aggressive OAuth application vacuuming ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30316))
### Fixed
- Fix leaking Elasticsearch connections in Sidekiq processes ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30450))
- Fix language of remote posts not being recognized when using unusual casing ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30403))
- Fix off-by-one in `tootctl media` commands ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30306))
- Fix removal of allowed domains (in `LIMITED_FEDERATION_MODE`) not being recorded in the audit log ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30125))
- Fix not being able to block a subdomain of an already-blocked domain through the API ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30119))
- Fix `Idempotency-Key` being ignored when scheduling a post ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30084))
- Fix crash when supplying the `FFMPEG_BINARY` environment variable ([timothyjrogers](https://github.com/mastodon/mastodon/pull/30022))
- Fix improper email address validation ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29838))
- Fix results/query in `api/v1/featured_tags/suggestions` ([mjankowski](https://github.com/mastodon/mastodon/pull/29597))
- Fix unblocking internationalized domain names under certain conditions ([tribela](https://github.com/mastodon/mastodon/pull/29530))
- Fix admin account created by `mastodon:setup` not being auto-approved ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29379))
- Fix reference to non-existent var in CLI maintenance command ([mjankowski](https://github.com/mastodon/mastodon/pull/28363))
## [4.2.8] - 2024-02-23
### Added
- Add hourly task to automatically require approval for new registrations in the absence of moderators ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29318), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/29355))
In order to prevent future abandoned Mastodon servers from being used for spam, harassment and other malicious activity, Mastodon will now automatically switch new user registrations to require moderator approval whenever they are left open and no activity (including non-moderation actions from apps) from any logged-in user with permission to access moderation reports has been detected in a full week.
When this happens, users with the permission to change server settings will receive an email notification.
This feature is disabled when `EMAIL_DOMAIN_ALLOWLIST` is used, and can also be disabled with `DISABLE_AUTOMATIC_SWITCHING_TO_APPROVED_REGISTRATIONS=true`.
### Changed
- Change registrations to be closed by default on new installations ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29280))
If you are running a server and never changed your registrations mode from the default, updating will automatically close your registrations.
Simply re-enable them through the administration interface or using `tootctl settings registrations open` if you want to enable them again.
### Fixed
- Fix processing of remote ActivityPub actors making use of `Link` objects as `Image` `url` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29335))
- Fix link verifications when page size exceeds 1MB ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29358))
## [4.2.7] - 2024-02-16
### Fixed

View file

@ -64,7 +64,7 @@ gem 'kaminari', '~> 1.2'
gem 'link_header', '~> 0.0'
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'mime-types', '~> 3.5.0', require: 'mime/types/columnar'
gem 'nokogiri', '~> 1.15'
gem 'nokogiri', '~> 1.16.5'
gem 'nsa'
gem 'oj', '~> 3.14'
gem 'ox', '~> 2.14'

View file

@ -454,7 +454,7 @@ GEM
net-smtp (0.5.0)
net-protocol
nio4r (2.7.1)
nokogiri (1.16.4)
nokogiri (1.16.5)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nsa (0.3.0)
@ -612,7 +612,8 @@ GEM
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
rexml (3.2.6)
rexml (3.2.8)
strscan (>= 3.0.9)
rotp (6.3.0)
rouge (4.2.1)
rpam2 (4.0.2)
@ -735,6 +736,7 @@ GEM
stringio (3.1.0)
strong_migrations (1.8.0)
activerecord (>= 5.2)
strscan (3.0.9)
swd (1.3.0)
activesupport (>= 3)
attr_required (>= 0.0.5)
@ -885,7 +887,7 @@ DEPENDENCIES
mime-types (~> 3.5.0)
net-http (~> 0.4.0)
net-ldap (~> 0.18)
nokogiri (~> 1.15)
nokogiri (~> 1.16.5)
nsa
oj (~> 3.14)
omniauth (~> 2.0)

View file

@ -3,6 +3,8 @@
class ActivityPub::Parser::StatusParser
include JsonLdHelper
NORMALIZED_LOCALE_NAMES = LanguagesHelper::SUPPORTED_LOCALES.keys.index_by(&:downcase).freeze
# @param [Hash] json
# @param [Hash] options
# @option options [String] :followers_collection
@ -118,10 +120,13 @@ class ActivityPub::Parser::StatusParser
end
def language
@language ||= original_language || (misskey_software? ? 'ja' : nil)
lang = raw_language_code || (misskey_software? ? 'ja' : nil)
lang.presence && NORMALIZED_LOCALE_NAMES.fetch(lang.downcase.to_sym, lang)
end
def original_language
private
def raw_language_code
if content_language_map?
@object['contentMap'].keys.first
elsif name_language_map?
@ -131,8 +136,6 @@ class ActivityPub::Parser::StatusParser
end
end
private
def audience_to
as_array(@object['to'] || @json['to']).map { |x| value_or_id(x) }
end

View file

@ -1,10 +0,0 @@
# frozen_string_literal: true
class Vacuum::ApplicationsVacuum
def perform
Doorkeeper::Application.where(owner_id: nil)
.where.missing(:created_users, :access_tokens, :access_grants)
.where(created_at: ...1.day.ago)
.in_batches.delete_all
end
end

View file

@ -150,6 +150,9 @@ class NotifyService < BaseService
end
def statuses_that_mention_sender
# This queries private mentions from the recipient to the sender up in the thread.
# This allows up to 100 messages that do not match in the thread, allowing conversations
# involving multiple people.
Status.count_by_sql([<<-SQL.squish, id: @notification.target_status.in_reply_to_id, recipient_id: @recipient.id, sender_id: @sender.id, depth_limit: 100])
WITH RECURSIVE ancestors(id, in_reply_to_id, mention_id, path, depth) AS (
SELECT s.id, s.in_reply_to_id, m.id, ARRAY[s.id], 0
@ -157,16 +160,17 @@ class NotifyService < BaseService
LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
WHERE s.id = :id
UNION ALL
SELECT s.id, s.in_reply_to_id, m.id, st.path || s.id, st.depth + 1
FROM ancestors st
JOIN statuses s ON s.id = st.in_reply_to_id
LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
WHERE st.mention_id IS NULL AND NOT s.id = ANY(path) AND st.depth < :depth_limit
SELECT s.id, s.in_reply_to_id, m.id, ancestors.path || s.id, ancestors.depth + 1
FROM ancestors
JOIN statuses s ON s.id = ancestors.in_reply_to_id
/* early exit if we already have a mention matching our requirements */
LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id AND s.account_id = :recipient_id
WHERE ancestors.mention_id IS NULL AND NOT s.id = ANY(path) AND ancestors.depth < :depth_limit
)
SELECT COUNT(*)
FROM ancestors st
JOIN statuses s ON s.id = st.id
WHERE st.mention_id IS NOT NULL AND s.visibility = 3
FROM ancestors
JOIN statuses s ON s.id = ancestors.id
WHERE ancestors.mention_id IS NOT NULL AND s.account_id = :recipient_id AND s.visibility = 3
SQL
end
end

View file

@ -22,7 +22,6 @@ class Scheduler::VacuumScheduler
preview_cards_vacuum,
backups_vacuum,
access_tokens_vacuum,
applications_vacuum,
feeds_vacuum,
imports_vacuum,
list_statuses_vacuum,
@ -62,10 +61,6 @@ class Scheduler::VacuumScheduler
Vacuum::ImportsVacuum.new
end
def applications_vacuum
Vacuum::ApplicationsVacuum.new
end
def ng_histories_vacuum
Vacuum::NgHistoriesVacuum.new
end

View file

@ -47,6 +47,7 @@ require_relative '../lib/chewy/strategy/bypass_with_warning'
require_relative '../lib/webpacker/manifest_extensions'
require_relative '../lib/webpacker/helper_extensions'
require_relative '../lib/rails/engine_extensions'
require_relative '../lib/action_dispatch/remote_ip_extensions'
require_relative '../lib/active_record/database_tasks_extensions'
require_relative '../lib/active_record/batches'
require_relative '../lib/simple_navigation/item_extensions'

View file

@ -37,6 +37,10 @@ class Rack::Attack
authenticated_token&.id
end
def warden_user_id
@env['warden']&.user&.id
end
def unauthenticated?
!authenticated_user_id
end
@ -58,10 +62,6 @@ class Rack::Attack
end
end
Rack::Attack.safelist('allow from localhost') do |req|
req.remote_ip == '127.0.0.1' || req.remote_ip == '::1'
end
Rack::Attack.blocklist('deny from blocklist') do |req|
IpBlock.blocked?(req.remote_ip)
end
@ -105,6 +105,10 @@ class Rack::Attack
req.authenticated_user_id if (req.post? && req.path.match?(API_DELETE_REBLOG_REGEX)) || (req.delete? && req.path.match?(API_DELETE_STATUS_REGEX))
end
throttle('throttle_oauth_application_registrations/ip', limit: 5, period: 10.minutes) do |req|
req.throttleable_remote_ip if req.post? && req.path == '/api/v1/apps'
end
throttle('throttle_sign_up_attempts/ip', limit: 25, period: 5.minutes) do |req|
req.throttleable_remote_ip if req.post? && req.path_matches?('/auth')
end
@ -137,6 +141,12 @@ class Rack::Attack
req.session[:attempt_user_id] || req.params.dig('user', 'email').presence if req.post? && req.path_matches?('/auth/sign_in')
end
API_CREATE_EMOJI_REACTION_REGEX = %r{\A/api/v1/statuses/\d+/emoji_reactions}
throttle('throttle_password_change/account', limit: 10, period: 10.minutes) do |req|
req.warden_user_id if (req.put? && !req.path.match?(API_CREATE_EMOJI_REACTION_REGEX)) || (req.patch? && req.path_matches?('/auth'))
end
self.throttled_responder = lambda do |request|
now = Time.now.utc
match_data = request.env['rack.attack.match_data']

View file

@ -1,4 +1,3 @@
version: '3'
services:
db:
restart: always
@ -56,7 +55,7 @@ services:
web:
build: .
image: ghcr.io/mastodon/mastodon:v4.2.7
image: ghcr.io/mastodon/mastodon:v4.2.9
restart: always
env_file: .env.production
command: bundle exec puma -C config/puma.rb
@ -76,10 +75,8 @@ services:
- ./public/system:/mastodon/public/system
streaming:
build:
context: .
dockerfile: streaming/Dockerfile
image: ghcr.io/mastodon/mastodon:v4.2.7
build: .
image: ghcr.io/mastodon/mastodon:v4.2.9
restart: always
env_file: .env.production
command: node ./streaming
@ -97,7 +94,7 @@ services:
sidekiq:
build: .
image: ghcr.io/mastodon/mastodon:v4.2.7
image: ghcr.io/mastodon/mastodon:v4.2.9
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.3
rbenv global 3.2.3
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 3.2.4
rbenv global 3.2.4
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.3
rbenv global 3.2.3
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 3.2.4
rbenv global 3.2.4
cat << EOF

View file

@ -0,0 +1,72 @@
# frozen_string_literal: true
# Mastodon is not made to be directly accessed without a reverse proxy.
# This monkey-patch prevents remote IP address spoofing when being accessed
# directly.
#
# See PR: https://github.com/rails/rails/pull/51610
# In addition to the PR above, it also raises an error if a request with
# `X-Forwarded-For` or `Client-Ip` comes directly from a client without
# going through a trusted proxy.
# rubocop:disable all -- This is a mostly vendored file
module ActionDispatch
class RemoteIp
module GetIpExtensions
def calculate_ip
# Set by the Rack web server, this is a single value.
remote_addr = ips_from(@req.remote_addr).last
# Could be a CSV list and/or repeated headers that were concatenated.
client_ips = ips_from(@req.client_ip).reverse!
forwarded_ips = ips_from(@req.x_forwarded_for).reverse!
# `Client-Ip` and `X-Forwarded-For` should not, generally, both be set. If they
# are both set, it means that either:
#
# 1) This request passed through two proxies with incompatible IP header
# conventions.
#
# 2) The client passed one of `Client-Ip` or `X-Forwarded-For`
# (whichever the proxy servers weren't using) themselves.
#
# Either way, there is no way for us to determine which header is the right one
# after the fact. Since we have no idea, if we are concerned about IP spoofing
# we need to give up and explode. (If you're not concerned about IP spoofing you
# can turn the `ip_spoofing_check` option off.)
should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
if should_check_ip && !forwarded_ips.include?(client_ips.last)
# We don't know which came from the proxy, and which from the user
raise IpSpoofAttackError, "IP spoofing attack?! " \
"HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \
"HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
end
# NOTE: Mastodon addition to make sure we don't get requests from a non-trusted client
if @check_ip && (forwarded_ips.last || client_ips.last) && !@proxies.any? { |proxy| proxy === remote_addr }
raise IpSpoofAttackError, "IP spoofing attack?! client #{remote_addr} is not a trusted proxy " \
"HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \
"HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
end
# We assume these things about the IP headers:
#
# - X-Forwarded-For will be a list of IPs, one per proxy, or blank
# - Client-Ip is propagated from the outermost proxy, or is blank
# - REMOTE_ADDR will be the IP that made the request to Rack
ips = forwarded_ips + client_ips
ips.compact!
# If every single IP option is in the trusted list, return the IP that's
# furthest away
filter_proxies([remote_addr] + ips).first || ips.last || remote_addr
end
end
end
end
ActionDispatch::RemoteIp::GetIp.prepend(ActionDispatch::RemoteIp::GetIpExtensions)
# rubocop:enable all

View file

@ -8,6 +8,7 @@ class Mastodon::SidekiqMiddleware
rescue Mastodon::HostValidationError
# Do not retry
rescue => e
clean_up_elasticsearch_connections!
limit_backtrace_and_raise(e)
ensure
clean_up_sockets!
@ -25,6 +26,32 @@ class Mastodon::SidekiqMiddleware
clean_up_statsd_socket!
end
# This is a hack to immediately free up unused Elasticsearch connections.
#
# Indeed, Chewy creates one `Elasticsearch::Client` instance per thread,
# and each such client manages its long-lasting connection to
# Elasticsearch.
#
# As far as I know, neither `chewy`, `elasticsearch-transport` or even
# `faraday` provide a reliable way to immediately close a connection, and
# rely on the underlying object to be garbage-collected instead.
#
# Furthermore, `sidekiq` creates a new thread each time a job throws an
# exception, meaning that each failure will create a new connection, and
# the old one will only be closed on full garbage collection.
def clean_up_elasticsearch_connections!
return unless Chewy.enabled? && Chewy.current[:chewy_client].present?
Chewy.client.transport.transport.connections.each do |connection|
# NOTE: This bit of code is tailored for the HTTPClient Faraday adapter
connection.connection.app.instance_variable_get(:@client)&.reset_all
end
Chewy.current.delete(:chewy_client)
rescue
nil
end
def clean_up_redis_socket!
RedisConfiguration.pool.checkin if Thread.current[:redis]
Thread.current[:redis] = nil

View file

@ -9,13 +9,13 @@ module Mastodon
end
def kmyblue_minor
0
2
end
def kmyblue_flag
# 'LTS'
'dev'
# nil
# 'dev'
nil
end
def major
@ -31,7 +31,7 @@ module Mastodon
end
def default_prerelease
'alpha.3'
'alpha.4'
end
def prerelease

View file

@ -7,7 +7,7 @@ describe Rack::Attack, type: :request do
Rails.application
end
shared_examples 'throttled endpoint' do
shared_context 'with throttled endpoint base' do
before do
# Rack::Attack periods are not rolling, so avoid flaky tests by setting the time in a way
# to avoid crossing period boundaries.
@ -19,6 +19,30 @@ describe Rack::Attack, type: :request do
travel_to Time.zone.at(counter_prefix * period.seconds)
end
def below_limit
limit - 1
end
def above_limit
limit * 2
end
def throttle_count
described_class.cache.read("#{counter_prefix}:#{throttle}:#{discriminator}") || 0
end
def counter_prefix
(Time.now.to_i / period.seconds).to_i
end
def increment_counter
described_class.cache.count("#{throttle}:#{discriminator}", period)
end
end
shared_examples 'throttled endpoint' do
include_examples 'with throttled endpoint base'
context 'when the number of requests is lower than the limit' do
before do
below_limit.times { increment_counter }
@ -46,29 +70,37 @@ describe Rack::Attack, type: :request do
expect(response).to_not have_http_status(429)
end
end
end
def below_limit
limit - 1
shared_examples 'does not throttle endpoint' do
include_examples 'with throttled endpoint base'
context 'when the number of requests is lower than the limit' do
before do
below_limit.times { increment_counter }
end
it 'does not change the request status' do
expect { request.call }.to change { throttle_count }.by(0)
expect(response).to_not have_http_status(429)
end
end
def above_limit
limit * 2
end
context 'when the number of requests is higher than the limit' do
before do
above_limit.times { increment_counter }
end
def throttle_count
described_class.cache.read("#{counter_prefix}:#{throttle}:#{remote_ip}") || 0
end
def counter_prefix
(Time.now.to_i / period.seconds).to_i
end
def increment_counter
described_class.cache.count("#{throttle}:#{remote_ip}", period)
it 'returns http too many requests after limit and returns to normal status after period' do
expect { request.call }.to change { throttle_count }.by(0)
expect(response).to_not have_http_status(429)
end
end
end
let(:remote_ip) { '1.2.3.5' }
let(:discriminator) { remote_ip }
describe 'throttle excessive sign-up requests by IP address' do
context 'when accessed through the website' do
@ -131,4 +163,68 @@ describe Rack::Attack, type: :request do
it_behaves_like 'throttled endpoint'
end
end
describe 'throttle excessive oauth application registration requests by IP address' do
let(:throttle) { 'throttle_oauth_application_registrations/ip' }
let(:limit) { 5 }
let(:period) { 10.minutes }
let(:path) { '/api/v1/apps' }
let(:params) do
{
client_name: 'Throttle Test',
redirect_uris: 'urn:ietf:wg:oauth:2.0:oob',
scopes: 'read',
}
end
let(:request) { -> { post path, params: params, headers: { 'REMOTE_ADDR' => remote_ip } } }
it_behaves_like 'throttled endpoint'
end
describe 'throttle excessive password change requests by account' do
let(:user) { Fabricate(:user, email: 'user@host.example') }
let(:throttle) { 'throttle_password_change/account' }
let(:limit) { 10 }
let(:period) { 10.minutes }
let(:request) { -> { put path, headers: { 'REMOTE_ADDR' => remote_ip } } }
let(:path) { '/auth' }
let(:discriminator) { user.id }
before do
sign_in user, scope: :user
# Unfortunately, devise's `sign_in` helper causes the `session` to be
# loaded in the next request regardless of whether it's actually accessed
# by the client code.
#
# So, we make an extra query to clear issue a session cookie instead.
#
# A less resource-intensive way to deal with that would be to generate the
# session cookie manually, but this seems pretty involved.
get '/'
end
it_behaves_like 'throttled endpoint'
end
describe 'throttle excessive emoji reaction requests by account' do
let(:user) { Fabricate(:user, email: 'user@host.example') }
let(:throttle) { 'throttle_password_change/account' }
let(:limit) { 10 }
let(:period) { 10.minutes }
let(:request) { -> { put path, headers: { 'REMOTE_ADDR' => remote_ip } } }
let(:status) { Fabricate(:status) }
let(:emoji) { Fabricate(:custom_emoji) }
let(:path) { "/api/v1/statuses/#{status.id}/emoji_reactions/#{emoji.shortcode}" }
let(:discriminator) { user.id }
before do
sign_in user, scope: :user
get '/'
end
it_behaves_like 'does not throttle endpoint'
end
end

View file

@ -0,0 +1,50 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ActivityPub::Parser::StatusParser do
subject { described_class.new(json) }
let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers', domain: 'example.com', uri: 'https://example.com/actor') }
let(:follower) { Fabricate(:account, username: 'bob') }
let(:json) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: [ActivityPub::TagManager.instance.uri_for(sender), '#foo'].join,
type: 'Create',
actor: ActivityPub::TagManager.instance.uri_for(sender),
object: object_json,
}.with_indifferent_access
end
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), 'post1'].join('/'),
type: 'Note',
to: [
'https://www.w3.org/ns/activitystreams#Public',
ActivityPub::TagManager.instance.uri_for(follower),
],
content: '@bob lorem ipsum',
contentMap: {
EN: '@bob lorem ipsum',
},
published: 1.hour.ago.utc.iso8601,
updated: 1.hour.ago.utc.iso8601,
tag: {
type: 'Mention',
href: ActivityPub::TagManager.instance.uri_for(follower),
},
}
end
it 'correctly parses status' do
expect(subject).to have_attributes(
text: '@bob lorem ipsum',
uri: [ActivityPub::TagManager.instance.uri_for(sender), 'post1'].join('/'),
reply: false,
language: :en
)
end
end

View file

@ -1,48 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Vacuum::ApplicationsVacuum do
subject { described_class.new }
describe '#perform' do
let!(:app_with_token) { Fabricate(:application, created_at: 1.month.ago) }
let!(:app_with_grant) { Fabricate(:application, created_at: 1.month.ago) }
let!(:app_with_signup) { Fabricate(:application, created_at: 1.month.ago) }
let!(:app_with_owner) { Fabricate(:application, created_at: 1.month.ago, owner: Fabricate(:user)) }
let!(:unused_app) { Fabricate(:application, created_at: 1.month.ago) }
let!(:recent_app) { Fabricate(:application, created_at: 1.hour.ago) }
before do
Fabricate(:access_token, application: app_with_token)
Fabricate(:access_grant, application: app_with_grant)
Fabricate(:user, created_by_application: app_with_signup)
subject.perform
end
it 'does not delete applications with valid access tokens' do
expect { app_with_token.reload }.to_not raise_error
end
it 'does not delete applications with valid access grants' do
expect { app_with_grant.reload }.to_not raise_error
end
it 'does not delete applications that were used to create users' do
expect { app_with_signup.reload }.to_not raise_error
end
it 'does not delete owned applications' do
expect { app_with_owner.reload }.to_not raise_error
end
it 'does not delete applications registered less than a day ago' do
expect { recent_app.reload }.to_not raise_error
end
it 'deletes unused applications' do
expect { unused_app.reload }.to raise_error ActiveRecord::RecordNotFound
end
end
end

View file

@ -309,6 +309,19 @@ RSpec.describe NotifyService do
expect(subject.filter?).to be false
end
end
context 'when the sender is mentioned in an unrelated message chain' do
before do
original_status = Fabricate(:status, visibility: :direct)
intermediary_status = Fabricate(:status, visibility: :direct, thread: original_status)
notification.target_status.update(thread: intermediary_status)
Fabricate(:mention, status: original_status, account: notification.from_account)
end
it 'returns true' do
expect(subject.filter?).to be true
end
end
end
end
end