47 changed files with 470 additions and 212 deletions
@ -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 |
||||
@ -1,11 +1,15 @@
|
||||
# frozen_string_literal: true |
||||
|
||||
class REST::Admin::DomainBlockSerializer < ActiveModel::Serializer |
||||
attributes :id, :domain, :created_at, :severity, |
||||
attributes :id, :domain, :digest, :created_at, :severity, |
||||
:reject_media, :reject_reports, |
||||
:private_comment, :public_comment, :obfuscate |
||||
|
||||
def id |
||||
object.id.to_s |
||||
end |
||||
|
||||
def digest |
||||
object.domain_digest |
||||
end |
||||
end |
||||
|
||||
@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true |
||||
|
||||
# NOTE: I initially wrote this as `EmailValidator` but it ended up clashing |
||||
# with an indirect dependency of ours, `validate_email`, which, turns out, |
||||
# has the same approach as we do, but with an extra check disallowing |
||||
# single-label domains. Decided to not switch to `validate_email` because |
||||
# we do want to allow at least `localhost`. |
||||
|
||||
class EmailAddressValidator < ActiveModel::EachValidator |
||||
def validate_each(record, attribute, value) |
||||
value = value.strip |
||||
|
||||
address = Mail::Address.new(value) |
||||
record.errors.add(attribute, :invalid) if address.address != value |
||||
rescue Mail::Field::FieldError |
||||
record.errors.add(attribute, :invalid) |
||||
end |
||||
end |
||||
@ -1,5 +1,6 @@
|
||||
# frozen_string_literal: true |
||||
|
||||
if ENV['FFMPEG_BINARY'].present? |
||||
FFMPEG.ffmpeg_binary = ENV['FFMPEG_BINARY'] |
||||
Rails.application.configure do |
||||
config.x.ffmpeg_binary = ENV['FFMPEG_BINARY'] || 'ffmpeg' |
||||
config.x.ffprobe_binary = ENV['FFPROBE_BINARY'] || 'ffprobe' |
||||
end |
||||
|
||||
@ -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 |
||||
@ -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 |
||||
@ -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) } |
||||
|
||||
let!(:active_access_token) { Fabricate(:access_token, application: app_with_token) } |
||||
let!(:active_access_grant) { Fabricate(:access_grant, application: app_with_grant) } |
||||
let!(:user) { Fabricate(:user, created_by_application: app_with_signup) } |
||||
|
||||
before do |
||||
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 |
||||
Loading…
Reference in new issue