Crowdin / Upload translations / upload-translations (push) Skipped
Historical data migration test / test (14-alpine) (push) Canceled after 0s
Historical data migration test / test (15-alpine) (push) Canceled after 0s
Historical data migration test / test (16-alpine) (push) Canceled after 0s
Historical data migration test / test (17-alpine) (push) Canceled after 0s
Bundler Audit / security (push) Canceled after 0s
Haml Linting / lint (push) Canceled after 0s
JavaScript Linting / lint (push) Canceled after 0s
Ruby Linting / lint (push) Canceled after 0s
JavaScript Testing / test (push) Canceled after 0s
Ruby Testing / build (production) (push) Canceled after 0s
Ruby Testing / test (.ruby-version) (push) Canceled after 0s
Ruby Testing / test (3.3) (push) Canceled after 0s
Ruby Testing / test (3.4) (push) Canceled after 0s
Ruby Testing / End to End testing (.ruby-version) (push) Canceled after 0s
Ruby Testing / End to End testing (3.3) (push) Canceled after 0s
Ruby Testing / End to End testing (3.4) (push) Canceled after 0s
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.29) (push) Canceled after 0s
Check i18n / check-i18n (push) Canceled after 0s
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (javascript) (push) Canceled after 0s
CodeQL / Analyze (ruby) (push) Canceled after 0s
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Canceled after 0s
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.29) (push) Canceled after 0s
Ruby Testing / Elastic Search integration testing (3.4, docker.elastic.co/elasticsearch/elasticsearch:7.17.29) (push) Canceled after 0s
Check formatting / lint (push) Canceled after 0s
CSS Linting / lint (push) Canceled after 0s
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.19.2) (push) Canceled after 0s
Chromatic / Check for relevant changes (push) Canceled after 0s
Chromatic / Run Chromatic (push) Canceled after 0s
Snapshot of upstream tag v4.7.3 (commit 8ff11c2bb7b9f0714e947b10fa3629c6542c7121) from https://github.com/mastodon/mastodon, unmodified.
107 lines
4.4 KiB
Ruby
107 lines
4.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class ActivityPub::ProcessActivityService < BaseService
|
|
include JsonLdHelper
|
|
include DomainControlHelper
|
|
|
|
def call(body, actor, **options)
|
|
@account = actor
|
|
@json = original_json = JSON.parse(body)
|
|
@options = options
|
|
|
|
return unless @json.is_a?(Hash)
|
|
|
|
ActivityPub::OpenTelemetry.decorate_current_span(payload: @json)
|
|
|
|
# Ideally, we should treat all ActivityPub payloads as proper JSON-LD.
|
|
# However, some implementations do not produce valid JSON-LD, and processing JSON-LD is pretty expensive.
|
|
# Therefore, we only process activities as JSON-LD if they make use of JSON-LD signatures.
|
|
# The reasons are that this is a strong indication that they are valid JSON-LD documents,
|
|
# and that Linked Data Signature verification requires JSON-LD processing to begin with.
|
|
# Doing JSON-LD compaction early ensures we know what we are working with, and avoids
|
|
# issues like attacks involving swapping external context documents between processing steps.
|
|
begin
|
|
@json = compact(@json) if @json['signature'].is_a?(Hash)
|
|
check_jsonld_limits!(@json)
|
|
rescue Mastodon::InvalidJsonLdError => e
|
|
Rails.logger.debug { "JSON-LD handling for document from #{value_or_id(@json['actor'])} skipped: #{e.message}" }
|
|
@json = original_json.without('signature')
|
|
rescue JSON::LD::JsonLdError => e
|
|
Rails.logger.debug { "Error when compacting JSON-LD document for #{value_or_id(@json['actor'])}: #{e.message}" }
|
|
@json = original_json.without('signature')
|
|
end
|
|
|
|
return unless supported_context?(@json)
|
|
|
|
if different_actor?
|
|
# This has been relayed by a different account.
|
|
# Record where it's coming from and try to verify proof of the activity.
|
|
|
|
@options[:relayed_through_actor] = @account
|
|
|
|
# Linked Data Signature verification
|
|
@account = actor_from_verified_ld_signature
|
|
|
|
# If Linked Data Signature verification failed, throw away the signature
|
|
# as other parts of the code use its presence as an indication of whether
|
|
# to forward the activity (don't throw away compaction though, it is still useful)
|
|
@json = @json.without('signature') if @account.nil?
|
|
|
|
# TODO: in the future, we might extend our forwarding rules to allow activities with
|
|
# FEP-8b32 Object Integrity Proofs to be forwarded.
|
|
# This would require keeping the original JSON around and changing forwarding logic in
|
|
# a few places. This is not worth it right now since FEP-8b32 is not widely supported,
|
|
# but could be worth doing in the future.
|
|
@account ||= actor_from_verified_object_integrity_proof(original_json)
|
|
end
|
|
|
|
return if !@account.is_a?(Account) || different_actor? || suspended_actor? || @account.local?
|
|
|
|
if @json['signature'].present?
|
|
# We have verified the signature, but in the compaction step above, might
|
|
# have introduced incompatibilities with other servers that do not
|
|
# normalize the JSON-LD documents (for instance, previous Mastodon
|
|
# versions), so skip redistribution if we can't get a safe document.
|
|
patch_for_forwarding!(original_json, @json)
|
|
@json.delete('signature') unless safe_for_forwarding?(original_json, @json)
|
|
end
|
|
|
|
activity = ActivityPub::Activity.factory(@json, @account, **@options)
|
|
activity&.perform
|
|
rescue JSON::ParserError
|
|
nil
|
|
end
|
|
|
|
private
|
|
|
|
def different_actor?
|
|
@json['actor'].present? && value_or_id(@json['actor']) != @account.uri
|
|
end
|
|
|
|
def suspended_actor?
|
|
@account.suspended? && !activity_allowed_while_suspended?
|
|
end
|
|
|
|
def activity_allowed_while_suspended?
|
|
%w(Delete Reject Undo Update).include?(@json['type'])
|
|
end
|
|
|
|
def actor_from_verified_ld_signature
|
|
return unless @json['signature'].is_a?(Hash)
|
|
return if domain_not_allowed?(@json['signature']['creator'])
|
|
|
|
ActivityPub::LinkedDataSignature.new(@json).verify_actor!
|
|
rescue JSON::LD::JsonLdError, RDF::WriterError => e
|
|
Rails.logger.debug { "Could not verify LD-Signature for #{value_or_id(@json['actor'])}: #{e.message}" }
|
|
nil
|
|
end
|
|
|
|
def actor_from_verified_object_integrity_proof(original_json)
|
|
return unless original_json['proof'].present? && original_json['actor'] == @json['actor']
|
|
return if domain_not_allowed?(@json['proof']['verificationMethod'])
|
|
|
|
# Verification is done on the original JSON without a signature
|
|
ActivityPub::ObjectIntegrityProof.new(original_json.without('signature')).verify_actor!
|
|
end
|
|
end
|