Browse Source

Mastodon 4.5.7 (#1389)

It figures a new release would come out immediately after we release
1.2.0 😅 This is a minor bugfix release though, and was very easy to
cherry-pick in. https://github.com/mastodon/mastodon/releases/tag/v4.5.7

cc @dariusk

---------

Co-authored-by: David Roetzel <david@roetzel.de>
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
Co-authored-by: ChaosExAnima <ChaosExAnima@users.noreply.github.com>
Co-authored-by: Matt Jankowski <matt@jankowski.online>
hometown-dev
Misty De Méo 3 weeks ago committed by GitHub
parent
commit
81c450c667
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 20
      CHANGELOG.md
  2. 2
      app/controllers/api/fasp/base_controller.rb
  3. 4
      app/helpers/json_ld_helper.rb
  4. 5
      app/javascript/mastodon/actions/statuses.js
  5. 9
      app/javascript/mastodon/features/emoji/loader.ts
  6. 2
      app/javascript/mastodon/reducers/compose.js
  7. 2
      app/lib/fasp/request.rb
  8. 2
      app/lib/request.rb
  9. 1
      app/models/fasp/provider.rb
  10. 2
      app/serializers/rest/base_quote_serializer.rb
  11. 2
      app/services/activitypub/fetch_remote_key_service.rb
  12. 2
      app/services/activitypub/process_status_update_service.rb
  13. 7
      app/services/block_domain_service.rb
  14. 2
      app/workers/fasp/base_worker.rb
  15. 15
      app/workers/purge_custom_emoji_worker.rb
  16. 6
      docker-compose.yml
  17. 16
      lib/mastodon/cli/emoji.rb
  18. 2
      lib/mastodon/version.rb
  19. 18
      spec/lib/fasp/request_spec.rb
  20. 30
      spec/lib/mastodon/cli/emoji_spec.rb
  21. 37
      spec/requests/api/fasp/data_sharing/v0/backfill_requests_spec.rb
  22. 21
      spec/requests/api/fasp/data_sharing/v0/continuations_spec.rb
  23. 58
      spec/requests/api/fasp/data_sharing/v0/event_subscriptions_spec.rb
  24. 25
      spec/requests/api/fasp/debug/v0/callback/responses_spec.rb
  25. 2
      spec/services/activitypub/fetch_remote_key_service_spec.rb
  26. 4
      spec/services/activitypub/process_status_update_service_spec.rb
  27. 13
      spec/support/examples/fasp/api.rb
  28. 4
      spec/workers/fasp/announce_account_lifecycle_event_worker_spec.rb
  29. 4
      spec/workers/fasp/announce_content_lifecycle_event_worker_spec.rb
  30. 3
      spec/workers/fasp/announce_trend_worker_spec.rb
  31. 4
      spec/workers/fasp/backfill_worker_spec.rb
  32. 33
      spec/workers/purge_custom_emoji_worker_spec.rb
  33. 7
      streaming/index.js

20
CHANGELOG.md

@ -2,6 +2,26 @@
All notable changes to this project will be documented in this file.
## [4.5.7] - 2026-02-24
### Security
- Reject unconfirmed FASPs (#37926 by @oneiros, [GHSA-qgmm-vr4c-ggjg](https://github.com/mastodon/mastodon/security/advisories/GHSA-qgmm-vr4c-ggjg))
- Re-use custom socket class for FASP requests (#37925 by @oneiros, [GHSA-46w6-g98f-wxqm](https://github.com/mastodon/mastodon/security/advisories/GHSA-46w6-g98f-wxqm))
### Added
- Add `--suspended-only` option to `tootctl emoji purge` (#37828 and #37861 by @ClearlyClaire and @mjankowski)
### Fixed
- Fix emoji data not being properly cached (#37858 by @ChaosExAnima)
- Fix delete & redraft of pending posts (#37839 by @ClearlyClaire)
- Fix processing separate key documents without the ActivityStreams context (#37826 by @ClearlyClaire)
- Fix custom emojis not being purged on domain suspension (#37808 by @ClearlyClaire)
- Fix users without special permissions being able to stream disabled timelines (#37791 by @ClearlyClaire)
- Fix processing of object updates with duplicate hashtags (#37756 by @ClearlyClaire)
## [4.5.6] - 2026-02-03
### Security

2
app/controllers/api/fasp/base_controller.rb

@ -47,7 +47,7 @@ class Api::Fasp::BaseController < ApplicationController
provider = nil
Linzer.verify!(request.rack_request, no_older_than: 5.minutes) do |keyid|
provider = Fasp::Provider.find(keyid)
provider = Fasp::Provider.confirmed.find(keyid)
Linzer.new_ed25519_public_key(provider.provider_public_key_pem, keyid)
end

4
app/helpers/json_ld_helper.rb

@ -70,6 +70,10 @@ module JsonLdHelper
!json.nil? && equals_or_includes?(json['@context'], ActivityPub::TagManager::CONTEXT)
end
def supported_security_context?(json)
!json.nil? && equals_or_includes?(json['@context'], 'https://w3id.org/security/v1')
end
def unsupported_uri_scheme?(uri)
uri.nil? || !uri.start_with?('http://', 'https://')
end

5
app/javascript/mastodon/actions/statuses.js

@ -109,7 +109,7 @@ export function fetchStatusFail(id, error, skipLoading, parentQuotePostId) {
};
}
export function redraft(status, raw_text) {
export function redraft(status, raw_text, quoted_status_id = null) {
return (dispatch, getState) => {
const maxOptions = getState().server.getIn(['server', 'configuration', 'polls', 'max_options']);
@ -117,6 +117,7 @@ export function redraft(status, raw_text) {
type: REDRAFT,
status,
raw_text,
quoted_status_id,
maxOptions,
});
};
@ -169,7 +170,7 @@ export function deleteStatus(id, withRedraft = false) {
dispatch(importFetchedAccount(response.data.account));
if (withRedraft) {
dispatch(redraft(status, response.data.text));
dispatch(redraft(status, response.data.text, response.data.quote?.quoted_status?.id));
ensureComposeIsVisible(getState);
} else {
dispatch(showAlert({ message: messages.deleteSuccess }));

9
app/javascript/mastodon/features/emoji/loader.ts

@ -21,10 +21,19 @@ export async function importEmojiData(localeString: string, path?: string) {
path ??= await localeToPath(locale);
}
// Fix from #37858. Check if we've loaded this path before.
const existing = await loadLatestEtag(locale);
if (existing === path) {
return null;
}
const emojis = await fetchAndCheckEtag<CompactEmoji[]>(locale, path);
if (!emojis) {
return;
}
await putLatestEtag(path, locale); // Fix from #37858. Put the path as the ETag to ensure we don't load the same data again.
const flattenedEmojis: FlatCompactEmoji[] = flattenEmojiData(emojis);
await putEmojiData(flattenedEmojis, locale);
return flattenedEmojis;

2
app/javascript/mastodon/reducers/compose.js

@ -540,7 +540,7 @@ export const composeReducer = (state = initialState, action) => {
map.set('sensitive', action.status.get('sensitive'));
map.set('language', action.status.get('language'));
map.set('id', null);
map.set('quoted_status_id', action.status.getIn(['quote', 'quoted_status'], null));
map.set('quoted_status_id', action.quoted_status_id);
// Mastodon-authored posts can be expected to have at most one automatic approval policy
map.set('quote_policy', action.status.getIn(['quote_approval', 'automatic', 0]) || 'nobody');

2
app/lib/fasp/request.rb

@ -29,7 +29,7 @@ class Fasp::Request
response = HTTP
.headers(headers)
.use(http_signature: { key:, covered_components: COVERED_COMPONENTS })
.send(verb, url, body:)
.send(verb, url, body:, socket_class: ::Request::Socket)
validate!(response)
@provider.delivery_failure_tracker.track_success!

2
app/lib/request.rb

@ -349,5 +349,5 @@ class Request
end
end
private_constant :ClientLimit, :Socket, :ProxySocket
private_constant :ClientLimit
end

1
app/models/fasp/provider.rb

@ -37,6 +37,7 @@ class Fasp::Provider < ApplicationRecord
before_create :create_keypair
after_commit :update_remote_capabilities
scope :confirmed, -> { where(confirmed: true) }
scope :with_capability, lambda { |capability_name|
where('fasp_providers.capabilities @> ?::jsonb', "[{\"id\": \"#{capability_name}\", \"enabled\": true}]")
}

2
app/serializers/rest/base_quote_serializer.rb

@ -13,7 +13,7 @@ class REST::BaseQuoteSerializer < ActiveModel::Serializer
end
def quoted_status
object.quoted_status if object.accepted? && object.quoted_status.present? && !object.quoted_status&.reblog? && status_filter.filter_state_for_quote != 'unauthorized'
object.quoted_status if (object.accepted? || instance_options[:source_requested]) && object.quoted_status.present? && !object.quoted_status&.reblog? && status_filter.filter_state_for_quote != 'unauthorized'
end
private

2
app/services/activitypub/fetch_remote_key_service.rb

@ -12,7 +12,7 @@ class ActivityPub::FetchRemoteKeyService < BaseService
@json = fetch_resource(uri, false)
raise Error, "Unable to fetch key JSON at #{uri}" if @json.nil?
raise Error, "Unsupported JSON-LD context for document #{uri}" unless supported_context?(@json)
raise Error, "Unsupported JSON-LD context for document #{uri}" unless supported_context?(@json) || (supported_security_context?(@json) && @json['owner'].present? && !actor_type?)
raise Error, "Unexpected object type for key #{uri}" unless expected_type?
return find_actor(@json['id'], @json, suppress_errors) if actor_type?

2
app/services/activitypub/process_status_update_service.rb

@ -208,7 +208,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
Tag.find_or_create_by_names([tag]).filter(&:valid?)
rescue ActiveRecord::RecordInvalid
[]
end
end.uniq
return unless @status.distributable?

7
app/services/block_domain_service.rb

@ -29,7 +29,12 @@ class BlockDomainService < BaseService
suspend_accounts!
end
DomainClearMediaWorker.perform_async(domain_block.id) if domain_block.reject_media?
if domain_block.suspend?
# Account images and attachments are already handled by `suspend_accounts!`
PurgeCustomEmojiWorker.perform_async(blocked_domain)
elsif domain_block.reject_media?
DomainClearMediaWorker.perform_async(domain_block.id)
end
end
def silence_accounts!

2
app/workers/fasp/base_worker.rb

@ -8,7 +8,7 @@ class Fasp::BaseWorker
private
def with_provider(provider)
return unless provider.available?
return unless provider.confirmed? && provider.available?
yield
rescue *Mastodon::HTTP_CONNECTION_ERRORS

15
app/workers/purge_custom_emoji_worker.rb

@ -0,0 +1,15 @@
# frozen_string_literal: true
class PurgeCustomEmojiWorker
include Sidekiq::IterableJob
def build_enumerator(domain, cursor:)
return if domain.blank?
active_record_batches_enumerator(CustomEmoji.by_domain_and_subdomains(domain), cursor:)
end
def each_iteration(custom_emojis, _domain)
AttachmentBatch.new(CustomEmoji, custom_emojis).delete
end
end

6
docker-compose.yml

@ -59,7 +59,7 @@ services:
web:
# You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
# build: .
image: ghcr.io/mastodon/mastodon:v4.5.6
image: ghcr.io/mastodon/mastodon:v4.5.7
restart: always
env_file: .env.production
command: bundle exec puma -C config/puma.rb
@ -83,7 +83,7 @@ services:
# build:
# dockerfile: ./streaming/Dockerfile
# context: .
image: ghcr.io/mastodon/mastodon-streaming:v4.5.6
image: ghcr.io/mastodon/mastodon-streaming:v4.5.7
restart: always
env_file: .env.production
command: node ./streaming/index.js
@ -102,7 +102,7 @@ services:
sidekiq:
# You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
# build: .
image: ghcr.io/mastodon/mastodon:v4.5.6
image: ghcr.io/mastodon/mastodon:v4.5.7
restart: always
env_file: .env.production
command: bundle exec sidekiq

16
lib/mastodon/cli/emoji.rb

@ -109,15 +109,27 @@ module Mastodon::CLI
end
option :remote_only, type: :boolean
option :suspended_only, type: :boolean
desc 'purge', 'Remove all custom emoji'
long_desc <<-LONG_DESC
Removes all custom emoji.
With the --remote-only option, only remote emoji will be deleted.
With the --suspended-only option, only emoji from suspended servers will be deleted.
LONG_DESC
def purge
scope = options[:remote_only] ? CustomEmoji.remote : CustomEmoji
scope.in_batches.destroy_all
if options[:suspended_only]
DomainBlock.where(severity: :suspend).find_each do |domain_block|
CustomEmoji.by_domain_and_subdomains(domain_block.domain).find_in_batches do |custom_emojis|
AttachmentBatch.new(CustomEmoji, custom_emojis).delete
end
end
else
scope = options[:remote_only] ? CustomEmoji.remote : CustomEmoji
scope.in_batches.destroy_all
end
say('OK', :green)
end

2
lib/mastodon/version.rb

@ -13,7 +13,7 @@ module Mastodon
end
def patch
6
7
end
def default_prerelease

18
spec/lib/fasp/request_spec.rb

@ -78,6 +78,24 @@ RSpec.describe Fasp::Request do
expect(provider.delivery_failure_tracker.failures).to eq 1
end
end
context 'when the provider host name resolves to a private address' do
around do |example|
WebMock.disable!
example.run
WebMock.enable!
end
it 'raises Mastodon::ValidationError' do
resolver = instance_double(Resolv::DNS)
allow(resolver).to receive(:getaddresses).with('reqprov.example.com').and_return(%w(0.0.0.0 2001:db8::face))
allow(resolver).to receive(:timeouts=).and_return(nil)
allow(Resolv::DNS).to receive(:open).and_yield(resolver)
expect { subject.send(method, '/test_path') }.to raise_error(Mastodon::ValidationError)
end
end
end
describe '#get' do

30
spec/lib/mastodon/cli/emoji_spec.rb

@ -23,6 +23,36 @@ RSpec.describe Mastodon::CLI::Emoji do
.to output_results('OK')
end
end
context 'with --suspended-only and existing custom emoji on blocked servers' do
let(:blocked_domain) { 'evil.com' }
let(:blocked_subdomain) { 'subdomain.evil.org' }
let(:blocked_domain_without_emoji) { 'blocked.com' }
let(:silenced_domain) { 'silenced.com' }
let(:options) { { suspended_only: true } }
before do
Fabricate(:custom_emoji)
Fabricate(:custom_emoji, domain: blocked_domain)
Fabricate(:custom_emoji, domain: blocked_subdomain)
Fabricate(:custom_emoji, domain: silenced_domain)
Fabricate(:domain_block, severity: :suspend, domain: blocked_domain)
Fabricate(:domain_block, severity: :suspend, domain: 'evil.org')
Fabricate(:domain_block, severity: :suspend, domain: blocked_domain_without_emoji)
Fabricate(:domain_block, severity: :silence, domain: silenced_domain)
end
it 'reports a successful purge' do
expect { subject }
.to output_results('OK')
.and change { CustomEmoji.by_domain_and_subdomains(blocked_domain).count }.to(0)
.and change { CustomEmoji.by_domain_and_subdomains('evil.org').count }.to(0)
.and not_change { CustomEmoji.by_domain_and_subdomains(silenced_domain).count }
.and(not_change { CustomEmoji.local.count })
end
end
end
describe '#import' do

37
spec/requests/api/fasp/data_sharing/v0/backfill_requests_spec.rb

@ -6,34 +6,33 @@ RSpec.describe 'Api::Fasp::DataSharing::V0::BackfillRequests', feature: :fasp do
include ProviderRequestHelper
describe 'POST /api/fasp/data_sharing/v0/backfill_requests' do
let(:provider) { Fabricate(:fasp_provider) }
subject do
post api_fasp_data_sharing_v0_backfill_requests_path, headers:, params:, as: :json
end
let(:provider) { Fabricate(:confirmed_fasp) }
let(:params) { { category: 'content', maxCount: 10 } }
let(:headers) do
request_authentication_headers(provider,
url: api_fasp_data_sharing_v0_backfill_requests_url,
method: :post,
body: params)
end
it_behaves_like 'forbidden for unconfirmed provider'
context 'with valid parameters' do
it 'creates a new backfill request' do
params = { category: 'content', maxCount: 10 }
headers = request_authentication_headers(provider,
url: api_fasp_data_sharing_v0_backfill_requests_url,
method: :post,
body: params)
expect do
post api_fasp_data_sharing_v0_backfill_requests_path, headers:, params:, as: :json
end.to change(Fasp::BackfillRequest, :count).by(1)
expect { subject }.to change(Fasp::BackfillRequest, :count).by(1)
expect(response).to have_http_status(201)
end
end
context 'with invalid parameters' do
let(:params) { { category: 'unknown', maxCount: 10 } }
it 'does not create a backfill request' do
params = { category: 'unknown', maxCount: 10 }
headers = request_authentication_headers(provider,
url: api_fasp_data_sharing_v0_backfill_requests_url,
method: :post,
body: params)
expect do
post api_fasp_data_sharing_v0_backfill_requests_path, headers:, params:, as: :json
end.to_not change(Fasp::BackfillRequest, :count)
expect { subject }.to_not change(Fasp::BackfillRequest, :count)
expect(response).to have_http_status(422)
end
end

21
spec/requests/api/fasp/data_sharing/v0/continuations_spec.rb

@ -6,15 +6,22 @@ RSpec.describe 'Api::Fasp::DataSharing::V0::Continuations', feature: :fasp do
include ProviderRequestHelper
describe 'POST /api/fasp/data_sharing/v0/backfill_requests/:id/continuations' do
let(:backfill_request) { Fabricate(:fasp_backfill_request) }
let(:provider) { backfill_request.fasp_provider }
subject do
post api_fasp_data_sharing_v0_backfill_request_continuation_path(backfill_request), headers:, as: :json
end
it 'queues a job to continue the given backfill request' do
headers = request_authentication_headers(provider,
url: api_fasp_data_sharing_v0_backfill_request_continuation_url(backfill_request),
method: :post)
let(:provider) { Fabricate(:confirmed_fasp) }
let(:backfill_request) { Fabricate(:fasp_backfill_request, fasp_provider: provider) }
let(:headers) do
request_authentication_headers(provider,
url: api_fasp_data_sharing_v0_backfill_request_continuation_url(backfill_request),
method: :post)
end
post api_fasp_data_sharing_v0_backfill_request_continuation_path(backfill_request), headers:, as: :json
it_behaves_like 'forbidden for unconfirmed provider'
it 'queues a job to continue the given backfill request' do
subject
expect(response).to have_http_status(204)
expect(Fasp::BackfillWorker).to have_enqueued_sidekiq_job(backfill_request.id)
end

58
spec/requests/api/fasp/data_sharing/v0/event_subscriptions_spec.rb

@ -6,51 +6,57 @@ RSpec.describe 'Api::Fasp::DataSharing::V0::EventSubscriptions', feature: :fasp
include ProviderRequestHelper
describe 'POST /api/fasp/data_sharing/v0/event_subscriptions' do
let(:provider) { Fabricate(:fasp_provider) }
subject do
post api_fasp_data_sharing_v0_event_subscriptions_path, headers:, params:, as: :json
end
let(:provider) { Fabricate(:confirmed_fasp) }
let(:params) { { category: 'content', subscriptionType: 'lifecycle', maxBatchSize: 10 } }
let(:headers) do
request_authentication_headers(provider,
url: api_fasp_data_sharing_v0_event_subscriptions_url,
method: :post,
body: params)
end
it_behaves_like 'forbidden for unconfirmed provider'
context 'with valid parameters' do
it 'creates a new subscription' do
params = { category: 'content', subscriptionType: 'lifecycle', maxBatchSize: 10 }
headers = request_authentication_headers(provider,
url: api_fasp_data_sharing_v0_event_subscriptions_url,
method: :post,
body: params)
expect do
post api_fasp_data_sharing_v0_event_subscriptions_path, headers:, params:, as: :json
subject
end.to change(Fasp::Subscription, :count).by(1)
expect(response).to have_http_status(201)
end
end
context 'with invalid parameters' do
it 'does not create a subscription' do
params = { category: 'unknown' }
headers = request_authentication_headers(provider,
url: api_fasp_data_sharing_v0_event_subscriptions_url,
method: :post,
body: params)
let(:params) { { category: 'unknown' } }
expect do
post api_fasp_data_sharing_v0_event_subscriptions_path, headers:, params:, as: :json
end.to_not change(Fasp::Subscription, :count)
it 'does not create a subscription' do
expect { subject }.to_not change(Fasp::Subscription, :count)
expect(response).to have_http_status(422)
end
end
end
describe 'DELETE /api/fasp/data_sharing/v0/event_subscriptions/:id' do
let(:subscription) { Fabricate(:fasp_subscription) }
let(:provider) { subscription.fasp_provider }
subject do
delete api_fasp_data_sharing_v0_event_subscription_path(subscription), headers:, as: :json
end
it 'deletes the subscription' do
headers = request_authentication_headers(provider,
url: api_fasp_data_sharing_v0_event_subscription_url(subscription),
method: :delete)
let(:provider) { Fabricate(:confirmed_fasp) }
let!(:subscription) { Fabricate(:fasp_subscription, fasp_provider: provider) }
let(:headers) do
request_authentication_headers(provider,
url: api_fasp_data_sharing_v0_event_subscription_url(subscription),
method: :delete)
end
it_behaves_like 'forbidden for unconfirmed provider'
expect do
delete api_fasp_data_sharing_v0_event_subscription_path(subscription), headers:, as: :json
end.to change(Fasp::Subscription, :count).by(-1)
it 'deletes the subscription' do
expect { subject }.to change(Fasp::Subscription, :count).by(-1)
expect(response).to have_http_status(204)
end
end

25
spec/requests/api/fasp/debug/v0/callback/responses_spec.rb

@ -6,18 +6,23 @@ RSpec.describe 'Api::Fasp::Debug::V0::Callback::Responses', feature: :fasp do
include ProviderRequestHelper
describe 'POST /api/fasp/debug/v0/callback/responses' do
let(:provider) { Fabricate(:debug_fasp) }
subject do
post api_fasp_debug_v0_callback_responses_path, headers:, params: payload, as: :json
end
it 'create a record of the callback' do
payload = { test: 'call' }
headers = request_authentication_headers(provider,
url: api_fasp_debug_v0_callback_responses_url,
method: :post,
body: payload)
let(:provider) { Fabricate(:confirmed_fasp) }
let(:payload) { { test: 'call' } }
let(:headers) do
request_authentication_headers(provider,
url: api_fasp_debug_v0_callback_responses_url,
method: :post,
body: payload)
end
expect do
post api_fasp_debug_v0_callback_responses_path, headers:, params: payload, as: :json
end.to change(Fasp::DebugCallback, :count).by(1)
it_behaves_like 'forbidden for unconfirmed provider'
it 'create a record of the callback' do
expect { subject }.to change(Fasp::DebugCallback, :count).by(1)
expect(response).to have_http_status(201)
debug_callback = Fasp::DebugCallback.last

2
spec/services/activitypub/fetch_remote_key_service_spec.rb

@ -71,7 +71,7 @@ RSpec.describe ActivityPub::FetchRemoteKeyService do
let(:public_key_id) { 'https://example.com/alice-public-key.json' }
before do
stub_request(:get, public_key_id).to_return(body: Oj.dump(key_json.merge({ '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] })), headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, public_key_id).to_return(body: Oj.dump(key_json.merge({ '@context': ['https://w3id.org/security/v1'] })), headers: { 'Content-Type': 'application/activity+json' })
end
it 'returns the expected account' do

4
spec/services/activitypub/process_status_update_service_spec.rb

@ -259,6 +259,8 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
{ type: 'Hashtag', name: 'foo' },
{ type: 'Hashtag', name: 'bar' },
{ type: 'Hashtag', name: '#2024' },
{ type: 'Hashtag', name: 'Foo Bar' },
{ type: 'Hashtag', name: 'FooBar' },
],
}
end
@ -270,7 +272,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
it 'updates tags and featured tags' do
expect { subject.call(status, json, json) }
.to change { status.tags.reload.pluck(:name) }.from(contain_exactly('test', 'foo')).to(contain_exactly('foo', 'bar'))
.to change { status.tags.reload.pluck(:name) }.from(contain_exactly('test', 'foo')).to(contain_exactly('foo', 'bar', 'foobar'))
.and change { status.account.featured_tags.find_by(name: 'test').statuses_count }.by(-1)
.and change { status.account.featured_tags.find_by(name: 'bar').statuses_count }.by(1)
.and change { status.account.featured_tags.find_by(name: 'bar').last_status_at }.from(nil).to(be_present)

13
spec/support/examples/fasp/api.rb

@ -0,0 +1,13 @@
# frozen_string_literal: true
RSpec.shared_examples 'forbidden for unconfirmed provider' do
context 'when the requesting provider is unconfirmed' do
let(:provider) { Fabricate(:fasp_provider) }
it 'returns http unauthorized' do
subject
expect(response).to have_http_status(401)
end
end
end

4
spec/workers/fasp/announce_account_lifecycle_event_worker_spec.rb

@ -8,10 +8,10 @@ RSpec.describe Fasp::AnnounceAccountLifecycleEventWorker do
subject { described_class.new.perform(account_uri, 'new') }
let(:account_uri) { 'https://masto.example.com/accounts/1' }
let(:provider) { Fabricate(:confirmed_fasp) }
let(:subscription) do
Fabricate(:fasp_subscription, category: 'account')
Fabricate(:fasp_subscription, fasp_provider: provider, category: 'account')
end
let(:provider) { subscription.fasp_provider }
let(:path) { '/data_sharing/v0/announcements' }
let!(:stubbed_request) do

4
spec/workers/fasp/announce_content_lifecycle_event_worker_spec.rb

@ -8,10 +8,10 @@ RSpec.describe Fasp::AnnounceContentLifecycleEventWorker do
subject { described_class.new.perform(status_uri, 'new') }
let(:status_uri) { 'https://masto.example.com/status/1' }
let(:provider) { Fabricate(:confirmed_fasp) }
let(:subscription) do
Fabricate(:fasp_subscription)
Fabricate(:fasp_subscription, fasp_provider: provider)
end
let(:provider) { subscription.fasp_provider }
let(:path) { '/data_sharing/v0/announcements' }
let!(:stubbed_request) do

3
spec/workers/fasp/announce_trend_worker_spec.rb

@ -8,14 +8,15 @@ RSpec.describe Fasp::AnnounceTrendWorker do
subject { described_class.new.perform(status.id, 'favourite') }
let(:status) { Fabricate(:status) }
let(:provider) { Fabricate(:confirmed_fasp) }
let(:subscription) do
Fabricate(:fasp_subscription,
fasp_provider: provider,
category: 'content',
subscription_type: 'trends',
threshold_timeframe: 15,
threshold_likes: 2)
end
let(:provider) { subscription.fasp_provider }
let(:path) { '/data_sharing/v0/announcements' }
let!(:stubbed_request) do

4
spec/workers/fasp/backfill_worker_spec.rb

@ -7,8 +7,8 @@ RSpec.describe Fasp::BackfillWorker do
subject { described_class.new.perform(backfill_request.id) }
let(:backfill_request) { Fabricate(:fasp_backfill_request) }
let(:provider) { backfill_request.fasp_provider }
let(:provider) { Fabricate(:confirmed_fasp) }
let(:backfill_request) { Fabricate(:fasp_backfill_request, fasp_provider: provider) }
let(:status) { Fabricate(:status) }
let(:path) { '/data_sharing/v0/announcements' }

33
spec/workers/purge_custom_emoji_worker_spec.rb

@ -0,0 +1,33 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe PurgeCustomEmojiWorker do
let(:worker) { described_class.new }
let(:domain) { 'evil' }
before do
Fabricate(:custom_emoji)
Fabricate(:custom_emoji, domain: 'example.com')
Fabricate.times(5, :custom_emoji, domain: domain)
end
describe '#perform' do
context 'when domain is nil' do
it 'does not delete emojis' do
expect { worker.perform(nil) }
.to_not(change(CustomEmoji, :count))
end
end
context 'when passing a domain' do
it 'deletes emojis from this domain only' do
expect { worker.perform(domain) }
.to change { CustomEmoji.where(domain: domain).count }.to(0)
.and not_change { CustomEmoji.local.count }
.and(not_change { CustomEmoji.where(domain: 'example.com').count })
end
end
end
end

7
streaming/index.js

@ -375,6 +375,7 @@ const startServer = async () => {
req.scopes = result.rows[0].scopes.split(' ');
req.accountId = result.rows[0].account_id;
req.chosenLanguages = result.rows[0].chosen_languages;
req.permissions = result.rows[0].permissions;
return {
accessTokenId: result.rows[0].id,
@ -600,13 +601,13 @@ const startServer = async () => {
/**
* @param {string} kind
* @param {ResolvedAccount} account
* @param {Request} req
* @returns {Promise.<{ localAccess: boolean, remoteAccess: boolean }>}
*/
const getFeedAccessSettings = async (kind, account) => {
const getFeedAccessSettings = async (kind, req) => {
const access = { localAccess: true, remoteAccess: true };
if (account.permissions & PERMISSION_VIEW_FEEDS) {
if (req.permissions & PERMISSION_VIEW_FEEDS) {
return access;
}

Loading…
Cancel
Save