Merge security fixes of mastodon v4.0.13 (#1340)
There were some smaller merge conflicts (e.g. in `lib/version.rb`), but all of them were of smaller nature. Due to the fact that other v4.0.* versions are also included, it's a bit bigger than the other PR for 3.5. I won't repeat the changelog here, the upgrade is the usual *git pull and restart all mastodon processes*. --------- Co-authored-by: Michael Stanclift <mx@vmstan.com> Co-authored-by: Claire <claire.github-309c@sitedethib.com> Co-authored-by: Eugen Rochko <eugen@zeonfederated.com> Co-authored-by: Emelia Smith <ThisIsMissEm@users.noreply.github.com> Co-authored-by: Renaud Chaput <renchap@gmail.com> Co-authored-by: Daniel M Brasil <danielmbrasil@protonmail.com> Co-authored-by: yufushiro <62991447+yufushiro@users.noreply.github.com> Co-authored-by: Nicolai Søborg <NicolaiSoeborg@users.noreply.github.com> Co-authored-by: Essem <smswessem@gmail.com> Co-authored-by: Jakob Gillich <jakob@gillich.me> Co-authored-by: David Aaron <1858430+suddjian@users.noreply.github.com> Co-authored-by: Matt Jankowski <matt@jankowski.online>
This commit is contained in:
parent
3b69a29703
commit
13fa4f70cc
32
CHANGELOG.md
32
CHANGELOG.md
|
@ -5,8 +5,36 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## End of life notice
|
## End of life notice
|
||||||
|
|
||||||
**The 4.0.x branch will not receive any update after 2023-10-31.**
|
**The 4.0.x branch has reached its end of life and will not receive any further update.**
|
||||||
This means that no security fix will be made available for this branch after this date, and you will need to update to a more recent version (such as the 4.1.x branch) to receive security fixes.
|
This means that no security fix will be made available for this branch after this date, and you will need to update to a more recent version (such as the 4.2.x branch) to receive security fixes.
|
||||||
|
|
||||||
|
## [4.0.13] - 2024-02-01
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Fix insufficient origin validation (CVE-2024-23832, [GHSA-3fjr-858r-92rw](https://github.com/mastodon/mastodon/security/advisories/GHSA-3fjr-858r-92rw))
|
||||||
|
|
||||||
|
## [4.0.12] - 2023-10-10
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Change some worker lock TTLs to be shorter-lived ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27246))
|
||||||
|
- Change user archive export allowed period from 7 days to 6 days ([suddjian](https://github.com/mastodon/mastodon/pull/27200))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix mentions being matched in some URL query strings ([mjankowski](https://github.com/mastodon/mastodon/pull/25656))
|
||||||
|
- Fix multiple instances of the trend refresh scheduler sometimes running at once ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27253))
|
||||||
|
- Fix importer returning negative row estimates ([jgillich](https://github.com/mastodon/mastodon/pull/27258))
|
||||||
|
- Fix filtering audit log for entries about disabling 2FA ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27186))
|
||||||
|
- Fix tIME chunk not being properly removed from PNG uploads ([TheEssem](https://github.com/mastodon/mastodon/pull/27111))
|
||||||
|
- Fix inefficient queries in “Follows and followers” as well as several admin pages ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27116), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27306))
|
||||||
|
|
||||||
|
## [4.0.11] - 2023-09-20
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix post translation erroring out ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26990))
|
||||||
|
|
||||||
## [4.0.10] - 2023-09-19
|
## [4.0.10] - 2023-09-19
|
||||||
|
|
||||||
|
|
130
Gemfile.lock
130
Gemfile.lock
|
@ -10,40 +10,40 @@ GIT
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (6.1.7.4)
|
actioncable (6.1.7.6)
|
||||||
actionpack (= 6.1.7.4)
|
actionpack (= 6.1.7.6)
|
||||||
activesupport (= 6.1.7.4)
|
activesupport (= 6.1.7.6)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
actionmailbox (6.1.7.4)
|
actionmailbox (6.1.7.6)
|
||||||
actionpack (= 6.1.7.4)
|
actionpack (= 6.1.7.6)
|
||||||
activejob (= 6.1.7.4)
|
activejob (= 6.1.7.6)
|
||||||
activerecord (= 6.1.7.4)
|
activerecord (= 6.1.7.6)
|
||||||
activestorage (= 6.1.7.4)
|
activestorage (= 6.1.7.6)
|
||||||
activesupport (= 6.1.7.4)
|
activesupport (= 6.1.7.6)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
actionmailer (6.1.7.4)
|
actionmailer (6.1.7.6)
|
||||||
actionpack (= 6.1.7.4)
|
actionpack (= 6.1.7.6)
|
||||||
actionview (= 6.1.7.4)
|
actionview (= 6.1.7.6)
|
||||||
activejob (= 6.1.7.4)
|
activejob (= 6.1.7.6)
|
||||||
activesupport (= 6.1.7.4)
|
activesupport (= 6.1.7.6)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (6.1.7.4)
|
actionpack (6.1.7.6)
|
||||||
actionview (= 6.1.7.4)
|
actionview (= 6.1.7.6)
|
||||||
activesupport (= 6.1.7.4)
|
activesupport (= 6.1.7.6)
|
||||||
rack (~> 2.0, >= 2.0.9)
|
rack (~> 2.0, >= 2.0.9)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||||
actiontext (6.1.7.4)
|
actiontext (6.1.7.6)
|
||||||
actionpack (= 6.1.7.4)
|
actionpack (= 6.1.7.6)
|
||||||
activerecord (= 6.1.7.4)
|
activerecord (= 6.1.7.6)
|
||||||
activestorage (= 6.1.7.4)
|
activestorage (= 6.1.7.6)
|
||||||
activesupport (= 6.1.7.4)
|
activesupport (= 6.1.7.6)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (6.1.7.4)
|
actionview (6.1.7.6)
|
||||||
activesupport (= 6.1.7.4)
|
activesupport (= 6.1.7.6)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
|
@ -54,22 +54,22 @@ GEM
|
||||||
case_transform (>= 0.2)
|
case_transform (>= 0.2)
|
||||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||||
active_record_query_trace (1.8)
|
active_record_query_trace (1.8)
|
||||||
activejob (6.1.7.4)
|
activejob (6.1.7.6)
|
||||||
activesupport (= 6.1.7.4)
|
activesupport (= 6.1.7.6)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (6.1.7.4)
|
activemodel (6.1.7.6)
|
||||||
activesupport (= 6.1.7.4)
|
activesupport (= 6.1.7.6)
|
||||||
activerecord (6.1.7.4)
|
activerecord (6.1.7.6)
|
||||||
activemodel (= 6.1.7.4)
|
activemodel (= 6.1.7.6)
|
||||||
activesupport (= 6.1.7.4)
|
activesupport (= 6.1.7.6)
|
||||||
activestorage (6.1.7.4)
|
activestorage (6.1.7.6)
|
||||||
actionpack (= 6.1.7.4)
|
actionpack (= 6.1.7.6)
|
||||||
activejob (= 6.1.7.4)
|
activejob (= 6.1.7.6)
|
||||||
activerecord (= 6.1.7.4)
|
activerecord (= 6.1.7.6)
|
||||||
activesupport (= 6.1.7.4)
|
activesupport (= 6.1.7.6)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
mini_mime (>= 1.1.0)
|
mini_mime (>= 1.1.0)
|
||||||
activesupport (6.1.7.4)
|
activesupport (6.1.7.6)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
|
@ -174,7 +174,7 @@ GEM
|
||||||
cocoon (1.2.15)
|
cocoon (1.2.15)
|
||||||
coderay (1.1.3)
|
coderay (1.1.3)
|
||||||
color_diff (0.1)
|
color_diff (0.1)
|
||||||
concurrent-ruby (1.1.10)
|
concurrent-ruby (1.2.2)
|
||||||
connection_pool (2.3.0)
|
connection_pool (2.3.0)
|
||||||
cose (1.2.1)
|
cose (1.2.1)
|
||||||
cbor (~> 0.5.9)
|
cbor (~> 0.5.9)
|
||||||
|
@ -312,7 +312,7 @@ GEM
|
||||||
httplog (1.6.0)
|
httplog (1.6.0)
|
||||||
rack (>= 2.0)
|
rack (>= 2.0)
|
||||||
rainbow (>= 2.0.0)
|
rainbow (>= 2.0.0)
|
||||||
i18n (1.12.0)
|
i18n (1.14.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
i18n-tasks (1.0.12)
|
i18n-tasks (1.0.12)
|
||||||
activesupport (>= 4.0.2)
|
activesupport (>= 4.0.2)
|
||||||
|
@ -401,9 +401,9 @@ GEM
|
||||||
mime-types (3.4.1)
|
mime-types (3.4.1)
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2022.0105)
|
mime-types-data (3.2022.0105)
|
||||||
mini_mime (1.1.2)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.2)
|
mini_portile2 (2.8.4)
|
||||||
minitest (5.16.3)
|
minitest (5.20.0)
|
||||||
msgpack (1.5.4)
|
msgpack (1.5.4)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.1.1)
|
multipart-post (2.1.1)
|
||||||
|
@ -412,7 +412,7 @@ GEM
|
||||||
net-ssh (>= 2.6.5, < 8.0.0)
|
net-ssh (>= 2.6.5, < 8.0.0)
|
||||||
net-ssh (7.0.1)
|
net-ssh (7.0.1)
|
||||||
nio4r (2.5.9)
|
nio4r (2.5.9)
|
||||||
nokogiri (1.15.3)
|
nokogiri (1.15.4)
|
||||||
mini_portile2 (~> 2.8.2)
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nsa (0.2.8)
|
nsa (0.2.8)
|
||||||
|
@ -477,13 +477,13 @@ GEM
|
||||||
pry-rails (0.3.9)
|
pry-rails (0.3.9)
|
||||||
pry (>= 0.10.4)
|
pry (>= 0.10.4)
|
||||||
public_suffix (5.0.0)
|
public_suffix (5.0.0)
|
||||||
puma (5.6.5)
|
puma (5.6.7)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
pundit (2.2.0)
|
pundit (2.2.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.7.1)
|
racc (1.7.1)
|
||||||
rack (2.2.7)
|
rack (2.2.8)
|
||||||
rack-attack (6.6.1)
|
rack-attack (6.6.1)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
rack-cors (1.1.1)
|
rack-cors (1.1.1)
|
||||||
|
@ -498,20 +498,20 @@ GEM
|
||||||
rack
|
rack
|
||||||
rack-test (2.0.2)
|
rack-test (2.0.2)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rails (6.1.7.4)
|
rails (6.1.7.6)
|
||||||
actioncable (= 6.1.7.4)
|
actioncable (= 6.1.7.6)
|
||||||
actionmailbox (= 6.1.7.4)
|
actionmailbox (= 6.1.7.6)
|
||||||
actionmailer (= 6.1.7.4)
|
actionmailer (= 6.1.7.6)
|
||||||
actionpack (= 6.1.7.4)
|
actionpack (= 6.1.7.6)
|
||||||
actiontext (= 6.1.7.4)
|
actiontext (= 6.1.7.6)
|
||||||
actionview (= 6.1.7.4)
|
actionview (= 6.1.7.6)
|
||||||
activejob (= 6.1.7.4)
|
activejob (= 6.1.7.6)
|
||||||
activemodel (= 6.1.7.4)
|
activemodel (= 6.1.7.6)
|
||||||
activerecord (= 6.1.7.4)
|
activerecord (= 6.1.7.6)
|
||||||
activestorage (= 6.1.7.4)
|
activestorage (= 6.1.7.6)
|
||||||
activesupport (= 6.1.7.4)
|
activesupport (= 6.1.7.6)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 6.1.7.4)
|
railties (= 6.1.7.6)
|
||||||
sprockets-rails (>= 2.0.0)
|
sprockets-rails (>= 2.0.0)
|
||||||
rails-controller-testing (1.0.5)
|
rails-controller-testing (1.0.5)
|
||||||
actionpack (>= 5.0.1.rc1)
|
actionpack (>= 5.0.1.rc1)
|
||||||
|
@ -527,9 +527,9 @@ GEM
|
||||||
railties (>= 6.0.0, < 7)
|
railties (>= 6.0.0, < 7)
|
||||||
rails-settings-cached (0.6.6)
|
rails-settings-cached (0.6.6)
|
||||||
rails (>= 4.2.0)
|
rails (>= 4.2.0)
|
||||||
railties (6.1.7.4)
|
railties (6.1.7.6)
|
||||||
actionpack (= 6.1.7.4)
|
actionpack (= 6.1.7.6)
|
||||||
activesupport (= 6.1.7.4)
|
activesupport (= 6.1.7.6)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
|
@ -609,8 +609,8 @@ GEM
|
||||||
activerecord (>= 4.0.0)
|
activerecord (>= 4.0.0)
|
||||||
railties (>= 4.0.0)
|
railties (>= 4.0.0)
|
||||||
semantic_range (3.0.0)
|
semantic_range (3.0.0)
|
||||||
sidekiq (6.5.7)
|
sidekiq (6.5.11)
|
||||||
connection_pool (>= 2.2.5)
|
connection_pool (>= 2.2.5, < 3)
|
||||||
rack (~> 2.0)
|
rack (~> 2.0)
|
||||||
redis (>= 4.5.0, < 5)
|
redis (>= 4.5.0, < 5)
|
||||||
sidekiq-bulk (0.2.0)
|
sidekiq-bulk (0.2.0)
|
||||||
|
@ -718,14 +718,14 @@ GEM
|
||||||
rack-proxy (>= 0.6.1)
|
rack-proxy (>= 0.6.1)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
semantic_range (>= 2.3.0)
|
semantic_range (>= 2.3.0)
|
||||||
websocket-driver (0.7.5)
|
websocket-driver (0.7.6)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
wisper (2.0.1)
|
wisper (2.0.1)
|
||||||
xorcist (1.1.3)
|
xorcist (1.1.3)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
zeitwerk (2.6.8)
|
zeitwerk (2.6.12)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
|
|
@ -13,6 +13,5 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | ------------------ |
|
| ------- | ------------------ |
|
||||||
| 4.1.x | Yes |
|
| 4.1.x | Yes |
|
||||||
| 4.0.x | Until 2023-10-31 |
|
| 4.0.x | No |
|
||||||
| 3.5.x | Until 2023-12-31 |
|
| < 4.0 | No |
|
||||||
| < 3.5 | No |
|
|
||||||
|
|
|
@ -246,7 +246,7 @@ module SignatureVerification
|
||||||
stoplight_wrap_request { ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, ''), suppress_errors: false) }
|
stoplight_wrap_request { ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, ''), suppress_errors: false) }
|
||||||
elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
|
elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
|
||||||
account = ActivityPub::TagManager.instance.uri_to_actor(key_id)
|
account = ActivityPub::TagManager.instance.uri_to_actor(key_id)
|
||||||
account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false, suppress_errors: false) }
|
account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, suppress_errors: false) }
|
||||||
account
|
account
|
||||||
end
|
end
|
||||||
rescue Mastodon::PrivateNetworkAddressError => e
|
rescue Mastodon::PrivateNetworkAddressError => e
|
||||||
|
|
|
@ -157,8 +157,8 @@ module JsonLdHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_resource(uri, id, on_behalf_of = nil)
|
def fetch_resource(uri, id_is_known, on_behalf_of = nil)
|
||||||
unless id
|
unless id_is_known
|
||||||
json = fetch_resource_without_id_validation(uri, on_behalf_of)
|
json = fetch_resource_without_id_validation(uri, on_behalf_of)
|
||||||
|
|
||||||
return if !json.is_a?(Hash) || unsupported_uri_scheme?(json['id'])
|
return if !json.is_a?(Hash) || unsupported_uri_scheme?(json['id'])
|
||||||
|
|
|
@ -152,7 +152,9 @@ class ActivityPub::Activity
|
||||||
def fetch_remote_original_status
|
def fetch_remote_original_status
|
||||||
if object_uri.start_with?('http')
|
if object_uri.start_with?('http')
|
||||||
return if ActivityPub::TagManager.instance.local_uri?(object_uri)
|
return if ActivityPub::TagManager.instance.local_uri?(object_uri)
|
||||||
ActivityPub::FetchRemoteStatusService.new.call(object_uri, id: true, on_behalf_of: @account.followers.local.first)
|
|
||||||
|
|
||||||
|
ActivityPub::FetchRemoteStatusService.new.call(object_uri, on_behalf_of: @account.followers.local.first)
|
||||||
elsif @object['url'].present?
|
elsif @object['url'].present?
|
||||||
::FetchRemoteStatusService.new.call(@object['url'])
|
::FetchRemoteStatusService.new.call(@object['url'])
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,7 +19,7 @@ class ActivityPub::LinkedDataSignature
|
||||||
return unless type == 'RsaSignature2017'
|
return unless type == 'RsaSignature2017'
|
||||||
|
|
||||||
creator = ActivityPub::TagManager.instance.uri_to_actor(creator_uri)
|
creator = ActivityPub::TagManager.instance.uri_to_actor(creator_uri)
|
||||||
creator ||= ActivityPub::FetchRemoteKeyService.new.call(creator_uri, id: false)
|
creator ||= ActivityPub::FetchRemoteKeyService.new.call(creator_uri)
|
||||||
|
|
||||||
return if creator.nil?
|
return if creator.nil?
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,9 @@ class Importer::BaseImporter
|
||||||
# Estimate the amount of documents that would be indexed. Not exact!
|
# Estimate the amount of documents that would be indexed. Not exact!
|
||||||
# @returns [Integer]
|
# @returns [Integer]
|
||||||
def estimate!
|
def estimate!
|
||||||
ActiveRecord::Base.connection_pool.with_connection { |connection| connection.select_one("SELECT reltuples AS estimate FROM pg_class WHERE relname = '#{index.adapter.target.table_name}'")['estimate'].to_i }
|
reltuples = ActiveRecord::Base.connection_pool.with_connection { |connection| connection.select_one("SELECT reltuples FROM pg_class WHERE relname = '#{index.adapter.target.table_name}'")['reltuples'].to_i }
|
||||||
|
# If the table has never yet been vacuumed or analyzed, reltuples contains -1
|
||||||
|
[reltuples, 0].max
|
||||||
end
|
end
|
||||||
|
|
||||||
# Import data from the database into the index
|
# Import data from the database into the index
|
||||||
|
|
|
@ -46,7 +46,7 @@ class TranslationService::DeepL < TranslationService
|
||||||
|
|
||||||
raise UnexpectedResponseError unless json.is_a?(Hash)
|
raise UnexpectedResponseError unless json.is_a?(Hash)
|
||||||
|
|
||||||
Translation.new(text: json.dig('translations', 0, 'text'), detected_source_language: json.dig('translations', 0, 'detected_source_language')&.downcase, provider: 'DeepL.com')
|
Translation.new(text: Sanitize.fragment(json.dig('translations', 0, 'text'), Sanitize::Config::MASTODON_STRICT), detected_source_language: json.dig('translations', 0, 'detected_source_language')&.downcase, provider: 'DeepL.com')
|
||||||
rescue Oj::ParseError
|
rescue Oj::ParseError
|
||||||
raise UnexpectedResponseError
|
raise UnexpectedResponseError
|
||||||
end
|
end
|
||||||
|
|
|
@ -37,7 +37,7 @@ class TranslationService::LibreTranslate < TranslationService
|
||||||
|
|
||||||
raise UnexpectedResponseError unless json.is_a?(Hash)
|
raise UnexpectedResponseError unless json.is_a?(Hash)
|
||||||
|
|
||||||
Translation.new(text: json['translatedText'], detected_source_language: source_language, provider: 'LibreTranslate')
|
Translation.new(text: Sanitize.fragment(json['translatedText'], Sanitize::Config::MASTODON_STRICT), detected_source_language: source_language, provider: 'LibreTranslate')
|
||||||
rescue Oj::ParseError
|
rescue Oj::ParseError
|
||||||
raise UnexpectedResponseError
|
raise UnexpectedResponseError
|
||||||
end
|
end
|
||||||
|
|
|
@ -61,9 +61,9 @@ class Account < ApplicationRecord
|
||||||
trust_level
|
trust_level
|
||||||
)
|
)
|
||||||
|
|
||||||
USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i
|
USERNAME_RE = /[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?/i
|
||||||
MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[[:word:]\.\-]+[[:word:]]+)?)/i
|
MENTION_RE = %r{(?<![=/[:word:]])@((#{USERNAME_RE})(?:@[[:word:].-]+[[:word:]]+)?)}i
|
||||||
URL_PREFIX_RE = /\Ahttp(s?):\/\/[^\/]+/
|
URL_PREFIX_RE = %r{\Ahttp(s?)://[^/]+}
|
||||||
USERNAME_ONLY_RE = /\A#{USERNAME_RE}\z/i
|
USERNAME_ONLY_RE = /\A#{USERNAME_RE}\z/i
|
||||||
|
|
||||||
include Attachmentable
|
include Attachmentable
|
||||||
|
@ -114,8 +114,8 @@ class Account < ApplicationRecord
|
||||||
scope :searchable, -> { without_unapproved.without_suspended.where(moved_to_account_id: nil) }
|
scope :searchable, -> { without_unapproved.without_suspended.where(moved_to_account_id: nil) }
|
||||||
scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).left_outer_joins(:account_stat) }
|
scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).left_outer_joins(:account_stat) }
|
||||||
scope :followable_by, ->(account) { joins(arel_table.join(Follow.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(Follow.arel_table[:target_account_id]).and(Follow.arel_table[:account_id].eq(account.id))).join_sources).where(Follow.arel_table[:id].eq(nil)).joins(arel_table.join(FollowRequest.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(FollowRequest.arel_table[:target_account_id]).and(FollowRequest.arel_table[:account_id].eq(account.id))).join_sources).where(FollowRequest.arel_table[:id].eq(nil)) }
|
scope :followable_by, ->(account) { joins(arel_table.join(Follow.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(Follow.arel_table[:target_account_id]).and(Follow.arel_table[:account_id].eq(account.id))).join_sources).where(Follow.arel_table[:id].eq(nil)).joins(arel_table.join(FollowRequest.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(FollowRequest.arel_table[:target_account_id]).and(FollowRequest.arel_table[:account_id].eq(account.id))).join_sources).where(FollowRequest.arel_table[:id].eq(nil)) }
|
||||||
scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc, accounts.id desc')) }
|
scope :by_recent_status, -> { includes(:account_stat).merge(AccountStat.order('last_status_at DESC NULLS LAST')).references(:account_stat) }
|
||||||
scope :by_recent_sign_in, -> { order(Arel.sql('(case when users.current_sign_in_at is null then 1 else 0 end) asc, users.current_sign_in_at desc, accounts.id desc')) }
|
scope :by_recent_sign_in, -> { order(Arel.sql('users.current_sign_in_at DESC NULLS LAST')) }
|
||||||
scope :popular, -> { order('account_stats.followers_count desc') }
|
scope :popular, -> { order('account_stats.followers_count desc') }
|
||||||
scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches("%.#{domain}"))) }
|
scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches("%.#{domain}"))) }
|
||||||
scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) }
|
scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) }
|
||||||
|
|
|
@ -38,7 +38,7 @@ class Admin::ActionLogFilter
|
||||||
destroy_status: { target_type: 'Status', action: 'destroy' }.freeze,
|
destroy_status: { target_type: 'Status', action: 'destroy' }.freeze,
|
||||||
destroy_user_role: { target_type: 'UserRole', action: 'destroy' }.freeze,
|
destroy_user_role: { target_type: 'UserRole', action: 'destroy' }.freeze,
|
||||||
destroy_canonical_email_block: { target_type: 'CanonicalEmailBlock', action: 'destroy' }.freeze,
|
destroy_canonical_email_block: { target_type: 'CanonicalEmailBlock', action: 'destroy' }.freeze,
|
||||||
disable_2fa_user: { target_type: 'User', action: 'disable' }.freeze,
|
disable_2fa_user: { target_type: 'User', action: 'disable_2fa' }.freeze,
|
||||||
disable_custom_emoji: { target_type: 'CustomEmoji', action: 'disable' }.freeze,
|
disable_custom_emoji: { target_type: 'CustomEmoji', action: 'disable' }.freeze,
|
||||||
disable_user: { target_type: 'User', action: 'disable' }.freeze,
|
disable_user: { target_type: 'User', action: 'disable' }.freeze,
|
||||||
enable_custom_emoji: { target_type: 'CustomEmoji', action: 'enable' }.freeze,
|
enable_custom_emoji: { target_type: 'CustomEmoji', action: 'enable' }.freeze,
|
||||||
|
|
|
@ -18,7 +18,7 @@ module AccountAvatar
|
||||||
|
|
||||||
included do
|
included do
|
||||||
# Avatar upload
|
# Avatar upload
|
||||||
has_attached_file :avatar, styles: ->(f) { avatar_styles(f) }, convert_options: { all: '+profile "!icc,*" +set modify-date +set create-date' }, processors: [:lazy_thumbnail]
|
has_attached_file :avatar, styles: ->(f) { avatar_styles(f) }, convert_options: { all: '+profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, processors: [:lazy_thumbnail]
|
||||||
validates_attachment_content_type :avatar, content_type: IMAGE_MIME_TYPES
|
validates_attachment_content_type :avatar, content_type: IMAGE_MIME_TYPES
|
||||||
validates_attachment_size :avatar, less_than: LIMIT
|
validates_attachment_size :avatar, less_than: LIMIT
|
||||||
remotable_attachment :avatar, LIMIT, suppress_errors: false
|
remotable_attachment :avatar, LIMIT, suppress_errors: false
|
||||||
|
|
|
@ -19,7 +19,7 @@ module AccountHeader
|
||||||
|
|
||||||
included do
|
included do
|
||||||
# Header upload
|
# Header upload
|
||||||
has_attached_file :header, styles: ->(f) { header_styles(f) }, convert_options: { all: '+profile "!icc,*" +set modify-date +set create-date' }, processors: [:lazy_thumbnail]
|
has_attached_file :header, styles: ->(f) { header_styles(f) }, convert_options: { all: '+profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, processors: [:lazy_thumbnail]
|
||||||
validates_attachment_content_type :header, content_type: IMAGE_MIME_TYPES
|
validates_attachment_content_type :header, content_type: IMAGE_MIME_TYPES
|
||||||
validates_attachment_size :header, less_than: LIMIT
|
validates_attachment_size :header, less_than: LIMIT
|
||||||
remotable_attachment :header, LIMIT, suppress_errors: false
|
remotable_attachment :header, LIMIT, suppress_errors: false
|
||||||
|
|
|
@ -37,7 +37,7 @@ class CustomEmoji < ApplicationRecord
|
||||||
belongs_to :category, class_name: 'CustomEmojiCategory', optional: true
|
belongs_to :category, class_name: 'CustomEmojiCategory', optional: true
|
||||||
has_one :local_counterpart, -> { where(domain: nil) }, class_name: 'CustomEmoji', primary_key: :shortcode, foreign_key: :shortcode
|
has_one :local_counterpart, -> { where(domain: nil) }, class_name: 'CustomEmoji', primary_key: :shortcode, foreign_key: :shortcode
|
||||||
|
|
||||||
has_attached_file :image, styles: { static: { format: 'png', convert_options: '-coalesce +profile "!icc,*" +set modify-date +set create-date' } }, validate_media_type: false
|
has_attached_file :image, styles: { static: { format: 'png', convert_options: '-coalesce +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' } }, validate_media_type: false
|
||||||
|
|
||||||
before_validation :downcase_domain
|
before_validation :downcase_domain
|
||||||
|
|
||||||
|
|
|
@ -168,7 +168,7 @@ class MediaAttachment < ApplicationRecord
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
GLOBAL_CONVERT_OPTIONS = {
|
GLOBAL_CONVERT_OPTIONS = {
|
||||||
all: '-quality 90 +profile "!icc,*" +set modify-date +set create-date',
|
all: '-quality 90 +profile "!icc,*" +set date:modify +set date:create +set date:timestamp',
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
belongs_to :account, inverse_of: :media_attachments, optional: true
|
belongs_to :account, inverse_of: :media_attachments, optional: true
|
||||||
|
|
|
@ -50,7 +50,7 @@ class PreviewCard < ApplicationRecord
|
||||||
has_and_belongs_to_many :statuses
|
has_and_belongs_to_many :statuses
|
||||||
has_one :trend, class_name: 'PreviewCardTrend', inverse_of: :preview_card, dependent: :destroy
|
has_one :trend, class_name: 'PreviewCardTrend', inverse_of: :preview_card, dependent: :destroy
|
||||||
|
|
||||||
has_attached_file :image, processors: [:thumbnail, :blurhash_transcoder], styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 90 +profile "!icc,*" +set modify-date +set create-date' }, validate_media_type: false
|
has_attached_file :image, processors: [:thumbnail, :blurhash_transcoder], styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 90 +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, validate_media_type: false
|
||||||
|
|
||||||
validates :url, presence: true, uniqueness: true
|
validates :url, presence: true, uniqueness: true
|
||||||
validates_attachment_content_type :image, content_type: IMAGE_MIME_TYPES
|
validates_attachment_content_type :image, content_type: IMAGE_MIME_TYPES
|
||||||
|
|
|
@ -25,7 +25,7 @@ class PreviewCardProvider < ApplicationRecord
|
||||||
|
|
||||||
validates :domain, presence: true, uniqueness: true, domain: true
|
validates :domain, presence: true, uniqueness: true, domain: true
|
||||||
|
|
||||||
has_attached_file :icon, styles: { static: { format: 'png', convert_options: '-coalesce +profile "!icc,*" +set modify-date +set create-date' } }, validate_media_type: false
|
has_attached_file :icon, styles: { static: { format: 'png', convert_options: '-coalesce +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' } }, validate_media_type: false
|
||||||
validates_attachment :icon, content_type: { content_type: ICON_MIME_TYPES }, size: { less_than: LIMIT }
|
validates_attachment :icon, content_type: { content_type: ICON_MIME_TYPES }, size: { less_than: LIMIT }
|
||||||
remotable_attachment :icon, LIMIT
|
remotable_attachment :icon, LIMIT
|
||||||
|
|
||||||
|
|
|
@ -60,13 +60,13 @@ class RelationshipFilter
|
||||||
def relationship_scope(value)
|
def relationship_scope(value)
|
||||||
case value
|
case value
|
||||||
when 'following'
|
when 'following'
|
||||||
account.following.eager_load(:account_stat).reorder(nil)
|
account.following.includes(:account_stat).reorder(nil)
|
||||||
when 'followed_by'
|
when 'followed_by'
|
||||||
account.followers.eager_load(:account_stat).reorder(nil)
|
account.followers.includes(:account_stat).reorder(nil)
|
||||||
when 'mutual'
|
when 'mutual'
|
||||||
account.followers.eager_load(:account_stat).reorder(nil).merge(Account.where(id: account.following))
|
account.followers.includes(:account_stat).reorder(nil).merge(Account.where(id: account.following))
|
||||||
when 'invited'
|
when 'invited'
|
||||||
Account.joins(user: :invite).merge(Invite.where(user: account.user)).eager_load(:account_stat).reorder(nil)
|
Account.joins(user: :invite).merge(Invite.where(user: account.user)).includes(:account_stat).reorder(nil)
|
||||||
else
|
else
|
||||||
raise Mastodon::InvalidParameterError, "Unknown relationship: #{value}"
|
raise Mastodon::InvalidParameterError, "Unknown relationship: #{value}"
|
||||||
end
|
end
|
||||||
|
@ -112,7 +112,7 @@ class RelationshipFilter
|
||||||
def activity_scope(value)
|
def activity_scope(value)
|
||||||
case value
|
case value
|
||||||
when 'dormant'
|
when 'dormant'
|
||||||
AccountStat.where(last_status_at: nil).or(AccountStat.where(AccountStat.arel_table[:last_status_at].lt(1.month.ago)))
|
Account.joins(:account_stat).where(account_stat: { last_status_at: [nil, ...1.month.ago] })
|
||||||
else
|
else
|
||||||
raise Mastodon::InvalidParameterError, "Unknown activity: #{value}"
|
raise Mastodon::InvalidParameterError, "Unknown activity: #{value}"
|
||||||
end
|
end
|
||||||
|
|
|
@ -40,7 +40,7 @@ class SiteUpload < ApplicationRecord
|
||||||
mascot: {}.freeze,
|
mascot: {}.freeze,
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
has_attached_file :file, styles: ->(file) { STYLES[file.instance.var.to_sym] }, convert_options: { all: '-coalesce +profile "!icc,*" +set modify-date +set create-date' }, processors: [:lazy_thumbnail, :blurhash_transcoder, :type_corrector]
|
has_attached_file :file, styles: ->(file) { STYLES[file.instance.var.to_sym] }, convert_options: { all: '-coalesce +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, processors: [:lazy_thumbnail, :blurhash_transcoder, :type_corrector]
|
||||||
|
|
||||||
validates_attachment_content_type :file, content_type: /\Aimage\/.*\z/
|
validates_attachment_content_type :file, content_type: /\Aimage\/.*\z/
|
||||||
validates :file, presence: true
|
validates :file, presence: true
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class BackupPolicy < ApplicationPolicy
|
class BackupPolicy < ApplicationPolicy
|
||||||
MIN_AGE = 1.week
|
MIN_AGE = 6.days
|
||||||
|
|
||||||
def create?
|
def create?
|
||||||
user_signed_in? && current_user.backups.where('created_at >= ?', MIN_AGE.ago).count.zero?
|
user_signed_in? && current_user.backups.where('created_at >= ?', MIN_AGE.ago).count.zero?
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
class ActivityPub::FetchRemoteAccountService < ActivityPub::FetchRemoteActorService
|
class ActivityPub::FetchRemoteAccountService < ActivityPub::FetchRemoteActorService
|
||||||
# Does a WebFinger roundtrip on each call, unless `only_key` is true
|
# Does a WebFinger roundtrip on each call, unless `only_key` is true
|
||||||
def call(uri, id: true, prefetched_body: nil, break_on_redirect: false, only_key: false, suppress_errors: true)
|
def call(uri, prefetched_body: nil, break_on_redirect: false, only_key: false, suppress_errors: true)
|
||||||
actor = super
|
actor = super
|
||||||
return actor if actor.nil? || actor.is_a?(Account)
|
return actor if actor.nil? || actor.is_a?(Account)
|
||||||
|
|
||||||
|
|
|
@ -10,15 +10,15 @@ class ActivityPub::FetchRemoteActorService < BaseService
|
||||||
SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze
|
SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze
|
||||||
|
|
||||||
# Does a WebFinger roundtrip on each call, unless `only_key` is true
|
# Does a WebFinger roundtrip on each call, unless `only_key` is true
|
||||||
def call(uri, id: true, prefetched_body: nil, break_on_redirect: false, only_key: false, suppress_errors: true)
|
def call(uri, prefetched_body: nil, break_on_redirect: false, only_key: false, suppress_errors: true)
|
||||||
return if domain_not_allowed?(uri)
|
return if domain_not_allowed?(uri)
|
||||||
return ActivityPub::TagManager.instance.uri_to_actor(uri) if ActivityPub::TagManager.instance.local_uri?(uri)
|
return ActivityPub::TagManager.instance.uri_to_actor(uri) if ActivityPub::TagManager.instance.local_uri?(uri)
|
||||||
|
|
||||||
@json = begin
|
@json = begin
|
||||||
if prefetched_body.nil?
|
if prefetched_body.nil?
|
||||||
fetch_resource(uri, id)
|
fetch_resource(uri, true)
|
||||||
else
|
else
|
||||||
body_to_json(prefetched_body, compare_id: id ? uri : nil)
|
body_to_json(prefetched_body, compare_id: uri)
|
||||||
end
|
end
|
||||||
rescue Oj::ParseError
|
rescue Oj::ParseError
|
||||||
raise Error, "Error parsing JSON-LD document #{uri}"
|
raise Error, "Error parsing JSON-LD document #{uri}"
|
||||||
|
|
|
@ -6,23 +6,10 @@ class ActivityPub::FetchRemoteKeyService < BaseService
|
||||||
class Error < StandardError; end
|
class Error < StandardError; end
|
||||||
|
|
||||||
# Returns actor that owns the key
|
# Returns actor that owns the key
|
||||||
def call(uri, id: true, prefetched_body: nil, suppress_errors: true)
|
def call(uri, suppress_errors: true)
|
||||||
raise Error, 'No key URI given' if uri.blank?
|
raise Error, 'No key URI given' if uri.blank?
|
||||||
|
|
||||||
if prefetched_body.nil?
|
@json = fetch_resource(uri, false)
|
||||||
if id
|
|
||||||
@json = fetch_resource_without_id_validation(uri)
|
|
||||||
if actor_type?
|
|
||||||
@json = fetch_resource(@json['id'], true)
|
|
||||||
elsif uri != @json['id']
|
|
||||||
raise Error, "Fetched URI #{uri} has wrong id #{@json['id']}"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
@json = fetch_resource(uri, id)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
@json = body_to_json(prefetched_body, compare_id: id ? uri : nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
raise Error, "Unable to fetch key JSON at #{uri}" if @json.nil?
|
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)
|
||||||
|
|
|
@ -4,12 +4,12 @@ class ActivityPub::FetchRemoteStatusService < BaseService
|
||||||
include JsonLdHelper
|
include JsonLdHelper
|
||||||
|
|
||||||
# Should be called when uri has already been checked for locality
|
# Should be called when uri has already been checked for locality
|
||||||
def call(uri, id: true, prefetched_body: nil, on_behalf_of: nil)
|
def call(uri, prefetched_body: nil, on_behalf_of: nil)
|
||||||
@json = begin
|
@json = begin
|
||||||
if prefetched_body.nil?
|
if prefetched_body.nil?
|
||||||
fetch_resource(uri, id, on_behalf_of)
|
fetch_resource(uri, true, on_behalf_of)
|
||||||
else
|
else
|
||||||
body_to_json(prefetched_body, compare_id: id ? uri : nil)
|
body_to_json(prefetched_body, compare_id: uri)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService
|
||||||
|
|
||||||
def account_from_uri(uri)
|
def account_from_uri(uri)
|
||||||
actor = ActivityPub::TagManager.instance.uri_to_resource(uri, Account)
|
actor = ActivityPub::TagManager.instance.uri_to_resource(uri, Account)
|
||||||
actor = ActivityPub::FetchRemoteAccountService.new.call(uri, id: true) if actor.nil? || actor.possibly_stale?
|
actor = ActivityPub::FetchRemoteAccountService.new.call(uri) if actor.nil? || actor.possibly_stale?
|
||||||
actor
|
actor
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -257,7 +257,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||||
|
|
||||||
def moved_account
|
def moved_account
|
||||||
account = ActivityPub::TagManager.instance.uri_to_resource(@json['movedTo'], Account)
|
account = ActivityPub::TagManager.instance.uri_to_resource(@json['movedTo'], Account)
|
||||||
account ||= ActivityPub::FetchRemoteAccountService.new.call(@json['movedTo'], id: true, break_on_redirect: true)
|
account ||= ActivityPub::FetchRemoteAccountService.new.call(@json['movedTo'], break_on_redirect: true)
|
||||||
account
|
account
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,15 @@ class FetchResourceService < BaseService
|
||||||
body = response.body_with_limit
|
body = response.body_with_limit
|
||||||
json = body_to_json(body)
|
json = body_to_json(body)
|
||||||
|
|
||||||
[json['id'], { prefetched_body: body, id: true }] if supported_context?(json) && (equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteActorService::SUPPORTED_TYPES) || expected_type?(json))
|
return unless supported_context?(json) && (equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteActorService::SUPPORTED_TYPES) || expected_type?(json))
|
||||||
|
|
||||||
|
if json['id'] != @url
|
||||||
|
return if terminal
|
||||||
|
|
||||||
|
return process(json['id'], terminal: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
[@url, { prefetched_body: body }]
|
||||||
elsif !terminal
|
elsif !terminal
|
||||||
link_header = response['Link'] && parse_link_header(response)
|
link_header = response['Link'] && parse_link_header(response)
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,8 @@ class TranslateStatusService < BaseService
|
||||||
@content = status_content_format(@status)
|
@content = status_content_format(@status)
|
||||||
@target_language = target_language
|
@target_language = target_language
|
||||||
|
|
||||||
Rails.cache.fetch("translations/#{@status.language}/#{@target_language}/#{content_hash}", expires_in: CACHE_TTL) do
|
Rails.cache.fetch("translations:v2/#{@status.language}/#{@target_language}/#{content_hash}", expires_in: CACHE_TTL) do
|
||||||
Sanitize.fragment(translation_backend.translate(@content, @status.language, @target_language), Sanitize::Config::MASTODON_STRICT)
|
translation_backend.translate(@content, @status.language, @target_language)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class AccountDeletionWorker
|
class AccountDeletionWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
sidekiq_options queue: 'pull', lock: :until_executed
|
sidekiq_options queue: 'pull', lock: :until_executed, lock_ttl: 1.week.to_i
|
||||||
|
|
||||||
def perform(account_id, options = {})
|
def perform(account_id, options = {})
|
||||||
reserve_username = options.with_indifferent_access.fetch(:reserve_username, true)
|
reserve_username = options.with_indifferent_access.fetch(:reserve_username, true)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class ActivityPub::SynchronizeFeaturedCollectionWorker
|
class ActivityPub::SynchronizeFeaturedCollectionWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
sidekiq_options queue: 'pull', lock: :until_executed
|
sidekiq_options queue: 'pull', lock: :until_executed, lock_ttl: 1.day.to_i
|
||||||
|
|
||||||
def perform(account_id, options = {})
|
def perform(account_id, options = {})
|
||||||
options = { note: true, hashtag: false }.deep_merge(options.deep_symbolize_keys)
|
options = { note: true, hashtag: false }.deep_merge(options.deep_symbolize_keys)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class ActivityPub::SynchronizeFeaturedTagsCollectionWorker
|
class ActivityPub::SynchronizeFeaturedTagsCollectionWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
sidekiq_options queue: 'pull', lock: :until_executed
|
sidekiq_options queue: 'pull', lock: :until_executed, lock_ttl: 1.day.to_i
|
||||||
|
|
||||||
def perform(account_id, url)
|
def perform(account_id, url)
|
||||||
ActivityPub::FetchFeaturedTagsCollectionService.new.call(Account.find(account_id), url)
|
ActivityPub::FetchFeaturedTagsCollectionService.new.call(Account.find(account_id), url)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ActivityPub::UpdateDistributionWorker < ActivityPub::RawDistributionWorker
|
class ActivityPub::UpdateDistributionWorker < ActivityPub::RawDistributionWorker
|
||||||
sidekiq_options queue: 'push', lock: :until_executed
|
sidekiq_options queue: 'push', lock: :until_executed, lock_ttl: 1.day.to_i
|
||||||
|
|
||||||
# Distribute an profile update to servers that might have a copy
|
# Distribute an profile update to servers that might have a copy
|
||||||
# of the account in question
|
# of the account in question
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class Admin::AccountDeletionWorker
|
class Admin::AccountDeletionWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
sidekiq_options queue: 'pull', lock: :until_executed
|
sidekiq_options queue: 'pull', lock: :until_executed, lock_ttl: 1.week.to_i
|
||||||
|
|
||||||
def perform(account_id)
|
def perform(account_id)
|
||||||
DeleteAccountService.new.call(Account.find(account_id), reserve_username: true, reserve_email: true)
|
DeleteAccountService.new.call(Account.find(account_id), reserve_username: true, reserve_email: true)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class Admin::DomainPurgeWorker
|
class Admin::DomainPurgeWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
sidekiq_options queue: 'pull', lock: :until_executed
|
sidekiq_options queue: 'pull', lock: :until_executed, lock_ttl: 1.week.to_i
|
||||||
|
|
||||||
def perform(domain)
|
def perform(domain)
|
||||||
PurgeDomainService.new.call(domain)
|
PurgeDomainService.new.call(domain)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class PublishScheduledStatusWorker
|
class PublishScheduledStatusWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
sidekiq_options lock: :until_executed
|
sidekiq_options lock: :until_executed, lock_ttl: 1.hour.to_i
|
||||||
|
|
||||||
def perform(scheduled_status_id)
|
def perform(scheduled_status_id)
|
||||||
scheduled_status = ScheduledStatus.find(scheduled_status_id)
|
scheduled_status = ScheduledStatus.find(scheduled_status_id)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class ResolveAccountWorker
|
class ResolveAccountWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
sidekiq_options queue: 'pull', lock: :until_executed
|
sidekiq_options queue: 'pull', lock: :until_executed, lock_ttl: 1.day.to_i
|
||||||
|
|
||||||
def perform(uri)
|
def perform(uri)
|
||||||
ResolveAccountService.new.call(uri)
|
ResolveAccountService.new.call(uri)
|
||||||
|
|
|
@ -4,7 +4,7 @@ class Scheduler::IndexingScheduler
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
include Redisable
|
include Redisable
|
||||||
|
|
||||||
sidekiq_options retry: 0
|
sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 30.minutes.to_i
|
||||||
|
|
||||||
IMPORT_BATCH_SIZE = 1000
|
IMPORT_BATCH_SIZE = 1000
|
||||||
SCAN_BATCH_SIZE = 10 * IMPORT_BATCH_SIZE
|
SCAN_BATCH_SIZE = 10 * IMPORT_BATCH_SIZE
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class Scheduler::ScheduledStatusesScheduler
|
class Scheduler::ScheduledStatusesScheduler
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
sidekiq_options retry: 0
|
sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 1.hour.to_i
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
publish_scheduled_statuses!
|
publish_scheduled_statuses!
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class Scheduler::Trends::RefreshScheduler
|
class Scheduler::Trends::RefreshScheduler
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
sidekiq_options retry: 0
|
sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 30.minutes.to_i
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
Trends.refresh!
|
Trends.refresh!
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class VerifyAccountLinksWorker
|
class VerifyAccountLinksWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
sidekiq_options queue: 'default', retry: false, lock: :until_executed
|
sidekiq_options queue: 'default', retry: false, lock: :until_executed, lock_ttl: 1.hour.to_i
|
||||||
|
|
||||||
def perform(account_id)
|
def perform(account_id)
|
||||||
account = Account.find(account_id)
|
account = Account.find(account_id)
|
||||||
|
|
|
@ -56,7 +56,7 @@ services:
|
||||||
|
|
||||||
web:
|
web:
|
||||||
build: .
|
build: .
|
||||||
image: ghcr.io/mastodon/mastodon:v4.0.10
|
image: ghcr.io/mastodon/mastodon:v4.0.13
|
||||||
restart: always
|
restart: always
|
||||||
env_file: .env.production
|
env_file: .env.production
|
||||||
command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
|
command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
|
||||||
|
@ -77,7 +77,7 @@ services:
|
||||||
|
|
||||||
streaming:
|
streaming:
|
||||||
build: .
|
build: .
|
||||||
image: ghcr.io/mastodon/mastodon:v4.0.10
|
image: ghcr.io/mastodon/mastodon:v4.0.13
|
||||||
restart: always
|
restart: always
|
||||||
env_file: .env.production
|
env_file: .env.production
|
||||||
command: node ./streaming
|
command: node ./streaming
|
||||||
|
@ -95,7 +95,7 @@ services:
|
||||||
|
|
||||||
sidekiq:
|
sidekiq:
|
||||||
build: .
|
build: .
|
||||||
image: ghcr.io/mastodon/mastodon:v4.0.10
|
image: ghcr.io/mastodon/mastodon:v4.0.13
|
||||||
restart: always
|
restart: always
|
||||||
env_file: .env.production
|
env_file: .env.production
|
||||||
command: bundle exec sidekiq
|
command: bundle exec sidekiq
|
||||||
|
|
|
@ -13,7 +13,7 @@ module Mastodon
|
||||||
end
|
end
|
||||||
|
|
||||||
def patch
|
def patch
|
||||||
10
|
13
|
||||||
end
|
end
|
||||||
|
|
||||||
def flags
|
def flags
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
Fabricator(:account_stat) do
|
Fabricator(:account_stat) do
|
||||||
account nil
|
account { Fabricate.build(:account) }
|
||||||
statuses_count ""
|
statuses_count '123'
|
||||||
following_count ""
|
following_count '456'
|
||||||
followers_count ""
|
followers_count '789'
|
||||||
end
|
end
|
||||||
|
|
|
@ -689,7 +689,7 @@ RSpec.describe Account, type: :model do
|
||||||
expect(subject.match('Check this out https://medium.com/@alice/some-article#.abcdef123')).to be_nil
|
expect(subject.match('Check this out https://medium.com/@alice/some-article#.abcdef123')).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
xit 'does not match URL querystring' do
|
it 'does not match URL query string' do
|
||||||
expect(subject.match('https://example.com/?x=@alice')).to be_nil
|
expect(subject.match('https://example.com/?x=@alice')).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,32 +6,60 @@ describe RelationshipFilter do
|
||||||
let(:account) { Fabricate(:account) }
|
let(:account) { Fabricate(:account) }
|
||||||
|
|
||||||
describe '#results' do
|
describe '#results' do
|
||||||
context 'when default params are used' do
|
let(:account_of_7_months) { Fabricate(:account_stat, statuses_count: 1, last_status_at: 7.months.ago).account }
|
||||||
let(:subject) do
|
let(:account_of_1_day) { Fabricate(:account_stat, statuses_count: 1, last_status_at: 1.day.ago).account }
|
||||||
RelationshipFilter.new(account, 'order' => 'active').results
|
let(:account_of_3_days) { Fabricate(:account_stat, statuses_count: 1, last_status_at: 3.days.ago).account }
|
||||||
|
let(:silent_account) { Fabricate(:account_stat, statuses_count: 0, last_status_at: nil).account }
|
||||||
|
|
||||||
|
before do
|
||||||
|
account.follow!(account_of_7_months)
|
||||||
|
account.follow!(account_of_1_day)
|
||||||
|
account.follow!(account_of_3_days)
|
||||||
|
account.follow!(silent_account)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when ordering by last activity' do
|
||||||
|
context 'when not filtering' do
|
||||||
|
subject do
|
||||||
|
described_class.new(account, 'order' => 'active').results
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns followings ordered by last activity' do
|
||||||
|
expect(subject).to eq [account_of_1_day, account_of_3_days, account_of_7_months, silent_account]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
context 'when filtering for dormant accounts' do
|
||||||
add_following_account_with(last_status_at: 7.days.ago)
|
subject do
|
||||||
add_following_account_with(last_status_at: 1.day.ago)
|
described_class.new(account, 'order' => 'active', 'activity' => 'dormant').results
|
||||||
add_following_account_with(last_status_at: 3.days.ago)
|
end
|
||||||
|
|
||||||
|
it 'returns dormant followings ordered by last activity' do
|
||||||
|
expect(subject).to eq [account_of_7_months, silent_account]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when ordering by account creation' do
|
||||||
|
context 'when not filtering' do
|
||||||
|
subject do
|
||||||
|
described_class.new(account, 'order' => 'recent').results
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns followings ordered by last account creation' do
|
||||||
|
expect(subject).to eq [silent_account, account_of_3_days, account_of_1_day, account_of_7_months]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns followings ordered by last activity' do
|
context 'when filtering for dormant accounts' do
|
||||||
expected_result = account.following.eager_load(:account_stat).reorder(nil).by_recent_status
|
subject do
|
||||||
|
described_class.new(account, 'order' => 'recent', 'activity' => 'dormant').results
|
||||||
|
end
|
||||||
|
|
||||||
expect(subject).to eq expected_result
|
it 'returns dormant followings ordered by last activity' do
|
||||||
|
expect(subject).to eq [silent_account, account_of_7_months]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_following_account_with(last_status_at:)
|
|
||||||
following_account = Fabricate(:account)
|
|
||||||
Fabricate(:account_stat, account: following_account,
|
|
||||||
last_status_at: last_status_at,
|
|
||||||
statuses_count: 1,
|
|
||||||
following_count: 0,
|
|
||||||
followers_count: 0)
|
|
||||||
Fabricate(:follow, account: account, target_account: following_account).account
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,7 +16,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#call' do
|
describe '#call' do
|
||||||
let(:account) { subject.call('https://example.com/alice', id: true) }
|
let(:account) { subject.call('https://example.com/alice') }
|
||||||
|
|
||||||
shared_examples 'sets profile data' do
|
shared_examples 'sets profile data' do
|
||||||
it 'returns an account' do
|
it 'returns an account' do
|
||||||
|
|
|
@ -16,7 +16,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService, type: :service do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#call' do
|
describe '#call' do
|
||||||
let(:account) { subject.call('https://example.com/alice', id: true) }
|
let(:account) { subject.call('https://example.com/alice') }
|
||||||
|
|
||||||
shared_examples 'sets profile data' do
|
shared_examples 'sets profile data' do
|
||||||
it 'returns an account' do
|
it 'returns an account' do
|
||||||
|
|
|
@ -43,7 +43,7 @@ RSpec.describe ActivityPub::FetchRemoteKeyService, type: :service do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#call' do
|
describe '#call' do
|
||||||
let(:account) { subject.call(public_key_id, id: false) }
|
let(:account) { subject.call(public_key_id) }
|
||||||
|
|
||||||
context 'when the key is a sub-object from the actor' do
|
context 'when the key is a sub-object from the actor' do
|
||||||
before do
|
before do
|
||||||
|
|
|
@ -54,7 +54,7 @@ RSpec.describe FetchResourceService, type: :service do
|
||||||
|
|
||||||
let(:json) do
|
let(:json) do
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 'http://example.com/foo',
|
||||||
'@context': ActivityPub::TagManager::CONTEXT,
|
'@context': ActivityPub::TagManager::CONTEXT,
|
||||||
type: 'Note',
|
type: 'Note',
|
||||||
}.to_json
|
}.to_json
|
||||||
|
@ -79,14 +79,14 @@ RSpec.describe FetchResourceService, type: :service do
|
||||||
let(:content_type) { 'application/activity+json; charset=utf-8' }
|
let(:content_type) { 'application/activity+json; charset=utf-8' }
|
||||||
let(:body) { json }
|
let(:body) { json }
|
||||||
|
|
||||||
it { is_expected.to eq [1, { prefetched_body: body, id: true }] }
|
it { is_expected.to eq ['http://example.com/foo', { prefetched_body: body }] }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when content type is ld+json with profile' do
|
context 'when content type is ld+json with profile' do
|
||||||
let(:content_type) { 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' }
|
let(:content_type) { 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' }
|
||||||
let(:body) { json }
|
let(:body) { json }
|
||||||
|
|
||||||
it { is_expected.to eq [1, { prefetched_body: body, id: true }] }
|
it { is_expected.to eq ['http://example.com/foo', { prefetched_body: body }] }
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
@ -97,14 +97,14 @@ RSpec.describe FetchResourceService, type: :service do
|
||||||
context 'when link header is present' do
|
context 'when link header is present' do
|
||||||
let(:headers) { { 'Link' => '<http://example.com/foo>; rel="alternate"; type="application/activity+json"', } }
|
let(:headers) { { 'Link' => '<http://example.com/foo>; rel="alternate"; type="application/activity+json"', } }
|
||||||
|
|
||||||
it { is_expected.to eq [1, { prefetched_body: json, id: true }] }
|
it { is_expected.to eq ['http://example.com/foo', { prefetched_body: json }] }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when content type is text/html' do
|
context 'when content type is text/html' do
|
||||||
let(:content_type) { 'text/html' }
|
let(:content_type) { 'text/html' }
|
||||||
let(:body) { '<html><head><link rel="alternate" href="http://example.com/foo" type="application/activity+json"/></head></html>' }
|
let(:body) { '<html><head><link rel="alternate" href="http://example.com/foo" type="application/activity+json"/></head></html>' }
|
||||||
|
|
||||||
it { is_expected.to eq [1, { prefetched_body: json, id: true }] }
|
it { is_expected.to eq ['http://example.com/foo', { prefetched_body: json }] }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -139,6 +139,7 @@ describe ResolveURLService, type: :service do
|
||||||
stub_request(:get, url).to_return(status: 302, headers: { 'Location' => status_url })
|
stub_request(:get, url).to_return(status: 302, headers: { 'Location' => status_url })
|
||||||
body = ActiveModelSerializers::SerializableResource.new(status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter).to_json
|
body = ActiveModelSerializers::SerializableResource.new(status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter).to_json
|
||||||
stub_request(:get, status_url).to_return(body: body, headers: { 'Content-Type' => 'application/activity+json' })
|
stub_request(:get, status_url).to_return(body: body, headers: { 'Content-Type' => 'application/activity+json' })
|
||||||
|
stub_request(:get, uri).to_return(body: body, headers: { 'Content-Type' => 'application/activity+json' })
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns status by url' do
|
it 'returns status by url' do
|
||||||
|
|
Loading…
Reference in New Issue