Skip to content
Snippets Groups Projects
Unverified Commit 8cf7006d authored by Claire's avatar Claire Committed by GitHub
Browse files

Refactor ActivityPub handling to prepare for non-Account actors (#19212)

* Move ActivityPub::FetchRemoteAccountService to ActivityPub::FetchRemoteActorService

ActivityPub::FetchRemoteAccountService is kept as a wrapper for when the actor is
specifically required to be an Account

* Refactor SignatureVerification to allow non-Account actors

* fixup! Move ActivityPub::FetchRemoteAccountService to ActivityPub::FetchRemoteActorService

* Refactor ActivityPub::FetchRemoteKeyService to potentially return non-Account actors

* Refactor inbound ActivityPub payload processing to accept non-Account actors

* Refactor inbound ActivityPub processing to accept activities relayed through non-Account

* Refactor how Account key URIs are built

* Refactor Request and drop unused key_id_format parameter

* Rename ActivityPub::Dereferencer `signature_account` to `signature_actor`
parent 84aff598
No related branches found
No related tags found
No related merge requests found
Showing
with 157 additions and 133 deletions
...@@ -7,7 +7,7 @@ class AccountsController < ApplicationController ...@@ -7,7 +7,7 @@ class AccountsController < ApplicationController
include AccountControllerConcern include AccountControllerConcern
include SignatureAuthentication include SignatureAuthentication
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? } before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers before_action :set_cache_headers
before_action :set_body_classes before_action :set_body_classes
......
...@@ -6,7 +6,7 @@ class ActivityPub::ClaimsController < ActivityPub::BaseController ...@@ -6,7 +6,7 @@ class ActivityPub::ClaimsController < ActivityPub::BaseController
skip_before_action :authenticate_user! skip_before_action :authenticate_user!
before_action :require_signature! before_action :require_account_signature!
before_action :set_claim_result before_action :set_claim_result
def create def create
......
...@@ -4,7 +4,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController ...@@ -4,7 +4,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
include SignatureVerification include SignatureVerification
include AccountOwnedConcern include AccountOwnedConcern
before_action :require_signature!, if: :authorized_fetch_mode? before_action :require_account_signature!, if: :authorized_fetch_mode?
before_action :set_items before_action :set_items
before_action :set_size before_action :set_size
before_action :set_type before_action :set_type
......
...@@ -4,7 +4,7 @@ class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseContro ...@@ -4,7 +4,7 @@ class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseContro
include SignatureVerification include SignatureVerification
include AccountOwnedConcern include AccountOwnedConcern
before_action :require_signature! before_action :require_account_signature!
before_action :set_items before_action :set_items
before_action :set_cache_headers before_action :set_cache_headers
......
...@@ -6,7 +6,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController ...@@ -6,7 +6,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
include AccountOwnedConcern include AccountOwnedConcern
before_action :skip_unknown_actor_activity before_action :skip_unknown_actor_activity
before_action :require_signature! before_action :require_actor_signature!
skip_before_action :authenticate_user! skip_before_action :authenticate_user!
def create def create
...@@ -49,17 +49,17 @@ class ActivityPub::InboxesController < ActivityPub::BaseController ...@@ -49,17 +49,17 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
end end
def upgrade_account def upgrade_account
if signed_request_account.ostatus? if signed_request_account&.ostatus?
signed_request_account.update(last_webfingered_at: nil) signed_request_account.update(last_webfingered_at: nil)
ResolveAccountWorker.perform_async(signed_request_account.acct) ResolveAccountWorker.perform_async(signed_request_account.acct)
end end
DeliveryFailureTracker.reset!(signed_request_account.inbox_url) DeliveryFailureTracker.reset!(signed_request_actor.inbox_url)
end end
def process_collection_synchronization def process_collection_synchronization
raw_params = request.headers['Collection-Synchronization'] raw_params = request.headers['Collection-Synchronization']
return if raw_params.blank? || ENV['DISABLE_FOLLOWERS_SYNCHRONIZATION'] == 'true' return if raw_params.blank? || ENV['DISABLE_FOLLOWERS_SYNCHRONIZATION'] == 'true' || signed_request_account.nil?
# Re-using the syntax for signature parameters # Re-using the syntax for signature parameters
tree = SignatureParamsParser.new.parse(raw_params) tree = SignatureParamsParser.new.parse(raw_params)
...@@ -71,6 +71,6 @@ class ActivityPub::InboxesController < ActivityPub::BaseController ...@@ -71,6 +71,6 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
end end
def process_payload def process_payload
ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body, @account&.id) ActivityPub::ProcessingWorker.perform_async(signed_request_actor.id, body, @account&.id, signed_request_actor.class.name)
end end
end end
...@@ -6,7 +6,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController ...@@ -6,7 +6,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
include SignatureVerification include SignatureVerification
include AccountOwnedConcern include AccountOwnedConcern
before_action :require_signature!, if: :authorized_fetch_mode? before_action :require_account_signature!, if: :authorized_fetch_mode?
before_action :set_statuses before_action :set_statuses
before_action :set_cache_headers before_action :set_cache_headers
......
...@@ -7,7 +7,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController ...@@ -7,7 +7,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
DESCENDANTS_LIMIT = 60 DESCENDANTS_LIMIT = 60
before_action :require_signature!, if: :authorized_fetch_mode? before_action :require_account_signature!, if: :authorized_fetch_mode?
before_action :set_status before_action :set_status
before_action :set_cache_headers before_action :set_cache_headers
before_action :set_replies before_action :set_replies
......
...@@ -45,10 +45,14 @@ module SignatureVerification ...@@ -45,10 +45,14 @@ module SignatureVerification
end end
end end
def require_signature! def require_account_signature!
render plain: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account render plain: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account
end end
def require_actor_signature!
render plain: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_actor
end
def signed_request? def signed_request?
request.headers['Signature'].present? request.headers['Signature'].present?
end end
...@@ -68,7 +72,11 @@ module SignatureVerification ...@@ -68,7 +72,11 @@ module SignatureVerification
end end
def signed_request_account def signed_request_account
return @signed_request_account if defined?(@signed_request_account) signed_request_actor.is_a?(Account) ? signed_request_actor : nil
end
def signed_request_actor
return @signed_request_actor if defined?(@signed_request_actor)
raise SignatureVerificationError, 'Request not signed' unless signed_request? raise SignatureVerificationError, 'Request not signed' unless signed_request?
raise SignatureVerificationError, 'Incompatible request signature. keyId and signature are required' if missing_required_signature_parameters? raise SignatureVerificationError, 'Incompatible request signature. keyId and signature are required' if missing_required_signature_parameters?
...@@ -78,22 +86,22 @@ module SignatureVerification ...@@ -78,22 +86,22 @@ module SignatureVerification
verify_signature_strength! verify_signature_strength!
verify_body_digest! verify_body_digest!
account = account_from_key_id(signature_params['keyId']) actor = actor_from_key_id(signature_params['keyId'])
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if account.nil? raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil?
signature = Base64.decode64(signature_params['signature']) signature = Base64.decode64(signature_params['signature'])
compare_signed_string = build_signed_string compare_signed_string = build_signed_string
return account unless verify_signature(account, signature, compare_signed_string).nil? return actor unless verify_signature(actor, signature, compare_signed_string).nil?
account = stoplight_wrap_request { account.possibly_stale? ? account.refresh! : account_refresh_key(account) } actor = stoplight_wrap_request { actor_refresh_key!(actor) }
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if account.nil? raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil?
return account unless verify_signature(account, signature, compare_signed_string).nil? return actor unless verify_signature(actor, signature, compare_signed_string).nil?
fail_with! "Verification failed for #{account.username}@#{account.domain} #{account.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)" fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)"
rescue SignatureVerificationError => e rescue SignatureVerificationError => e
fail_with! e.message fail_with! e.message
rescue HTTP::Error, OpenSSL::SSL::SSLError => e rescue HTTP::Error, OpenSSL::SSL::SSLError => e
...@@ -112,7 +120,7 @@ module SignatureVerification ...@@ -112,7 +120,7 @@ module SignatureVerification
def fail_with!(message) def fail_with!(message)
@signature_verification_failure_reason = message @signature_verification_failure_reason = message
@signed_request_account = nil @signed_request_actor = nil
end end
def signature_params def signature_params
...@@ -160,10 +168,10 @@ module SignatureVerification ...@@ -160,10 +168,10 @@ module SignatureVerification
raise SignatureVerificationError, "Invalid Digest value. Computed SHA-256 digest: #{body_digest}; given: #{sha256[1]}" raise SignatureVerificationError, "Invalid Digest value. Computed SHA-256 digest: #{body_digest}; given: #{sha256[1]}"
end end
def verify_signature(account, signature, compare_signed_string) def verify_signature(actor, signature, compare_signed_string)
if account.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), signature, compare_signed_string) if actor.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), signature, compare_signed_string)
@signed_request_account = account @signed_request_actor = actor
@signed_request_account @signed_request_actor
end end
rescue OpenSSL::PKey::RSAError rescue OpenSSL::PKey::RSAError
nil nil
...@@ -226,7 +234,7 @@ module SignatureVerification ...@@ -226,7 +234,7 @@ module SignatureVerification
signature_params['keyId'].blank? || signature_params['signature'].blank? signature_params['keyId'].blank? || signature_params['signature'].blank?
end end
def account_from_key_id(key_id) def actor_from_key_id(key_id)
domain = key_id.start_with?('acct:') ? key_id.split('@').last : key_id domain = key_id.start_with?('acct:') ? key_id.split('@').last : key_id
if domain_not_allowed?(domain) if domain_not_allowed?(domain)
...@@ -237,13 +245,13 @@ module SignatureVerification ...@@ -237,13 +245,13 @@ module SignatureVerification
if key_id.start_with?('acct:') if key_id.start_with?('acct:')
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_resource(key_id, Account) 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, id: false, suppress_errors: false) }
account account
end end
rescue Mastodon::PrivateNetworkAddressError => e rescue Mastodon::PrivateNetworkAddressError => e
raise SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})" raise SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})"
rescue Mastodon::HostValidationError, ActivityPub::FetchRemoteAccountService::Error, ActivityPub::FetchRemoteKeyService::Error, Webfinger::Error => e rescue Mastodon::HostValidationError, ActivityPub::FetchRemoteActorService::Error, ActivityPub::FetchRemoteKeyService::Error, Webfinger::Error => e
raise SignatureVerificationError, e.message raise SignatureVerificationError, e.message
end end
...@@ -255,12 +263,14 @@ module SignatureVerification ...@@ -255,12 +263,14 @@ module SignatureVerification
.run .run
end end
def account_refresh_key(account) def actor_refresh_key!(actor)
return if account.local? || !account.activitypub? return if actor.local? || !actor.activitypub?
ActivityPub::FetchRemoteAccountService.new.call(account.uri, only_key: true, suppress_errors: false) return actor.refresh! if actor.respond_to?(:refresh!) && actor.possibly_stale?
ActivityPub::FetchRemoteActorService.new.call(actor.uri, only_key: true, suppress_errors: false)
rescue Mastodon::PrivateNetworkAddressError => e rescue Mastodon::PrivateNetworkAddressError => e
raise SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})" raise SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})"
rescue Mastodon::HostValidationError, ActivityPub::FetchRemoteAccountService::Error, Webfinger::Error => e rescue Mastodon::HostValidationError, ActivityPub::FetchRemoteActorService::Error, Webfinger::Error => e
raise SignatureVerificationError, e.message raise SignatureVerificationError, e.message
end end
end end
...@@ -4,7 +4,7 @@ class FollowerAccountsController < ApplicationController ...@@ -4,7 +4,7 @@ class FollowerAccountsController < ApplicationController
include AccountControllerConcern include AccountControllerConcern
include SignatureVerification include SignatureVerification
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? } before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers before_action :set_cache_headers
skip_around_action :set_locale, if: -> { request.format == :json } skip_around_action :set_locale, if: -> { request.format == :json }
......
...@@ -4,7 +4,7 @@ class FollowingAccountsController < ApplicationController ...@@ -4,7 +4,7 @@ class FollowingAccountsController < ApplicationController
include AccountControllerConcern include AccountControllerConcern
include SignatureVerification include SignatureVerification
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? } before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers before_action :set_cache_headers
skip_around_action :set_locale, if: -> { request.format == :json } skip_around_action :set_locale, if: -> { request.format == :json }
......
...@@ -8,7 +8,7 @@ class StatusesController < ApplicationController ...@@ -8,7 +8,7 @@ class StatusesController < ApplicationController
layout 'public' layout 'public'
before_action :require_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? } before_action :require_account_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_status before_action :set_status
before_action :set_instance_presenter before_action :set_instance_presenter
before_action :set_link_headers before_action :set_link_headers
......
...@@ -8,7 +8,7 @@ class TagsController < ApplicationController ...@@ -8,7 +8,7 @@ class TagsController < ApplicationController
layout 'public' layout 'public'
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? } before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :authenticate_user!, if: :whitelist_mode? before_action :authenticate_user!, if: :whitelist_mode?
before_action :set_local before_action :set_local
before_action :set_tag before_action :set_tag
......
...@@ -116,12 +116,12 @@ class ActivityPub::Activity ...@@ -116,12 +116,12 @@ class ActivityPub::Activity
def dereference_object! def dereference_object!
return unless @object.is_a?(String) return unless @object.is_a?(String)
dereferencer = ActivityPub::Dereferencer.new(@object, permitted_origin: @account.uri, signature_account: signed_fetch_account) dereferencer = ActivityPub::Dereferencer.new(@object, permitted_origin: @account.uri, signature_actor: signed_fetch_actor)
@object = dereferencer.object unless dereferencer.object.nil? @object = dereferencer.object unless dereferencer.object.nil?
end end
def signed_fetch_account def signed_fetch_actor
return Account.find(@options[:delivered_to_account_id]) if @options[:delivered_to_account_id].present? return Account.find(@options[:delivered_to_account_id]) if @options[:delivered_to_account_id].present?
first_mentioned_local_account || first_local_follower first_mentioned_local_account || first_local_follower
...@@ -163,15 +163,15 @@ class ActivityPub::Activity ...@@ -163,15 +163,15 @@ class ActivityPub::Activity
end end
def followed_by_local_accounts? def followed_by_local_accounts?
@account.passive_relationships.exists? || @options[:relayed_through_account]&.passive_relationships&.exists? @account.passive_relationships.exists? || (@options[:relayed_through_actor].is_a?(Account) && @options[:relayed_through_actor].passive_relationships&.exists?)
end end
def requested_through_relay? def requested_through_relay?
@options[:relayed_through_account] && Relay.find_by(inbox_url: @options[:relayed_through_account].inbox_url)&.enabled? @options[:relayed_through_actor] && Relay.find_by(inbox_url: @options[:relayed_through_actor].inbox_url)&.enabled?
end end
def reject_payload! def reject_payload!
Rails.logger.info("Rejected #{@json['type']} activity #{@json['id']} from #{@account.uri}#{@options[:relayed_through_account] && "via #{@options[:relayed_through_account].uri}"}") Rails.logger.info("Rejected #{@json['type']} activity #{@json['id']} from #{@account.uri}#{@options[:relayed_through_actor] && "via #{@options[:relayed_through_actor].uri}"}")
nil nil
end end
end end
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
class ActivityPub::Dereferencer class ActivityPub::Dereferencer
include JsonLdHelper include JsonLdHelper
def initialize(uri, permitted_origin: nil, signature_account: nil) def initialize(uri, permitted_origin: nil, signature_actor: nil)
@uri = uri @uri = uri
@permitted_origin = permitted_origin @permitted_origin = permitted_origin
@signature_account = signature_account @signature_actor = signature_actor
end end
def object def object
...@@ -46,7 +46,7 @@ class ActivityPub::Dereferencer ...@@ -46,7 +46,7 @@ class ActivityPub::Dereferencer
req.add_headers('Accept' => 'application/activity+json, application/ld+json') req.add_headers('Accept' => 'application/activity+json, application/ld+json')
req.add_headers(headers) if headers req.add_headers(headers) if headers
req.on_behalf_of(@signature_account) if @signature_account req.on_behalf_of(@signature_actor) if @signature_actor
req.perform do |res| req.perform do |res|
if res.code == 200 if res.code == 200
......
...@@ -9,7 +9,7 @@ class ActivityPub::LinkedDataSignature ...@@ -9,7 +9,7 @@ class ActivityPub::LinkedDataSignature
@json = json.with_indifferent_access @json = json.with_indifferent_access
end end
def verify_account! def verify_actor!
return unless @json['signature'].is_a?(Hash) return unless @json['signature'].is_a?(Hash)
type = @json['signature']['type'] type = @json['signature']['type']
...@@ -18,7 +18,7 @@ class ActivityPub::LinkedDataSignature ...@@ -18,7 +18,7 @@ class ActivityPub::LinkedDataSignature
return unless type == 'RsaSignature2017' return unless type == 'RsaSignature2017'
creator = ActivityPub::TagManager.instance.uri_to_resource(creator_uri, Account) 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, id: false)
return if creator.nil? return if creator.nil?
...@@ -35,7 +35,7 @@ class ActivityPub::LinkedDataSignature ...@@ -35,7 +35,7 @@ class ActivityPub::LinkedDataSignature
def sign!(creator, sign_with: nil) def sign!(creator, sign_with: nil)
options = { options = {
'type' => 'RsaSignature2017', 'type' => 'RsaSignature2017',
'creator' => [ActivityPub::TagManager.instance.uri_for(creator), '#main-key'].join, 'creator' => ActivityPub::TagManager.instance.key_uri_for(creator),
'created' => Time.now.utc.iso8601, 'created' => Time.now.utc.iso8601,
} }
......
...@@ -44,6 +44,10 @@ class ActivityPub::TagManager ...@@ -44,6 +44,10 @@ class ActivityPub::TagManager
end end
end end
def key_uri_for(target)
[uri_for(target), '#main-key'].join
end
def uri_for_username(username) def uri_for_username(username)
account_url(username: username) account_url(username: username)
end end
...@@ -155,6 +159,10 @@ class ActivityPub::TagManager ...@@ -155,6 +159,10 @@ class ActivityPub::TagManager
path_params[param] path_params[param]
end end
def uri_to_actor(uri)
uri_to_resource(uri, Account)
end
def uri_to_resource(uri, klass) def uri_to_resource(uri, klass)
return if uri.nil? return if uri.nil?
......
...@@ -40,12 +40,11 @@ class Request ...@@ -40,12 +40,11 @@ class Request
set_digest! if options.key?(:body) set_digest! if options.key?(:body)
end end
def on_behalf_of(account, key_id_format = :uri, sign_with: nil) def on_behalf_of(actor, sign_with: nil)
raise ArgumentError, 'account must not be nil' if account.nil? raise ArgumentError, 'actor must not be nil' if actor.nil?
@account = account @actor = actor
@keypair = sign_with.present? ? OpenSSL::PKey::RSA.new(sign_with) : @account.keypair @keypair = sign_with.present? ? OpenSSL::PKey::RSA.new(sign_with) : @actor.keypair
@key_id_format = key_id_format
self self
end end
...@@ -79,7 +78,7 @@ class Request ...@@ -79,7 +78,7 @@ class Request
end end
def headers def headers
(@account ? @headers.merge('Signature' => signature) : @headers).without(REQUEST_TARGET) (@actor ? @headers.merge('Signature' => signature) : @headers).without(REQUEST_TARGET)
end end
class << self class << self
...@@ -128,12 +127,7 @@ class Request ...@@ -128,12 +127,7 @@ class Request
end end
def key_id def key_id
case @key_id_format ActivityPub::TagManager.instance.key_uri_for(@actor)
when :acct
@account.to_webfinger_s
when :uri
[ActivityPub::TagManager.instance.uri_for(@account), '#main-key'].join
end
end end
def http_client def http_client
......
...@@ -6,7 +6,7 @@ class ActivityPub::PublicKeySerializer < ActivityPub::Serializer ...@@ -6,7 +6,7 @@ class ActivityPub::PublicKeySerializer < ActivityPub::Serializer
attributes :id, :owner, :public_key_pem attributes :id, :owner, :public_key_pem
def id def id
[ActivityPub::TagManager.instance.uri_for(object), '#main-key'].join ActivityPub::TagManager.instance.key_uri_for(object)
end end
def owner def owner
......
# frozen_string_literal: true # frozen_string_literal: true
class ActivityPub::FetchRemoteAccountService < BaseService class ActivityPub::FetchRemoteAccountService < ActivityPub::FetchRemoteActorService
include JsonLdHelper
include DomainControlHelper
include WebfingerHelper
class Error < StandardError; end
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, id: true, prefetched_body: nil, break_on_redirect: false, only_key: false, suppress_errors: true)
return if domain_not_allowed?(uri) actor = super
return ActivityPub::TagManager.instance.uri_to_resource(uri, Account) if ActivityPub::TagManager.instance.local_uri?(uri) return actor if actor.nil? || actor.is_a?(Account)
@json = begin
if prefetched_body.nil?
fetch_resource(uri, id)
else
body_to_json(prefetched_body, compare_id: id ? uri : nil)
end
rescue Oj::ParseError
raise Error, "Error parsing JSON-LD document #{uri}"
end
raise Error, "Error fetching actor JSON at #{uri}" if @json.nil?
raise Error, "Unsupported JSON-LD context for document #{uri}" unless supported_context?
raise Error, "Unexpected object type for actor #{uri} (expected any of: #{SUPPORTED_TYPES})" unless expected_type?
raise Error, "Actor #{uri} has moved to #{@json['movedTo']}" if break_on_redirect && @json['movedTo'].present?
@uri = @json['id']
@username = @json['preferredUsername']
@domain = Addressable::URI.parse(@uri).normalized_host
check_webfinger! unless only_key
ActivityPub::ProcessAccountService.new.call(@username, @domain, @json, only_key: only_key, verified_webfinger: !only_key)
rescue Error => e
Rails.logger.debug "Fetching account #{uri} failed: #{e.message}"
raise unless suppress_errors
end
private
def check_webfinger!
webfinger = webfinger!("acct:#{@username}@#{@domain}")
confirmed_username, confirmed_domain = split_acct(webfinger.subject)
if @username.casecmp(confirmed_username).zero? && @domain.casecmp(confirmed_domain).zero?
raise Error, "Webfinger response for #{@username}@#{@domain} does not loop back to #{@uri}" if webfinger.link('self', 'href') != @uri
return
end
webfinger = webfinger!("acct:#{confirmed_username}@#{confirmed_domain}")
@username, @domain = split_acct(webfinger.subject)
unless confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
raise Webfinger::RedirectError, "Too many webfinger redirects for URI #{uri} (stopped at #{@username}@#{@domain})"
end
raise Error, "Webfinger response for #{@username}@#{@domain} does not loop back to #{@uri}" if webfinger.link('self', 'href') != @uri
rescue Webfinger::RedirectError => e
raise Error, e.message
rescue Webfinger::Error => e
raise Error, "Webfinger error when resolving #{@username}@#{@domain}: #{e.message}"
end
def split_acct(acct)
acct.gsub(/\Aacct:/, '').split('@')
end
def supported_context?
super(@json)
end
def expected_type? Rails.logger.debug "Fetching account #{uri} failed: Expected Account, got #{actor.class.name}"
equals_or_includes_any?(@json['type'], SUPPORTED_TYPES) raise Error, "Expected Account, got #{actor.class.name}" unless suppress_errors
end end
end end
# frozen_string_literal: true
class ActivityPub::FetchRemoteActorService < BaseService
include JsonLdHelper
include DomainControlHelper
include WebfingerHelper
class Error < StandardError; end
SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze
# 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)
return if domain_not_allowed?(uri)
return ActivityPub::TagManager.instance.uri_to_actor(uri) if ActivityPub::TagManager.instance.local_uri?(uri)
@json = begin
if prefetched_body.nil?
fetch_resource(uri, id)
else
body_to_json(prefetched_body, compare_id: id ? uri : nil)
end
rescue Oj::ParseError
raise Error, "Error parsing JSON-LD document #{uri}"
end
raise Error, "Error fetching actor JSON at #{uri}" if @json.nil?
raise Error, "Unsupported JSON-LD context for document #{uri}" unless supported_context?
raise Error, "Unexpected object type for actor #{uri} (expected any of: #{SUPPORTED_TYPES})" unless expected_type?
raise Error, "Actor #{uri} has moved to #{@json['movedTo']}" if break_on_redirect && @json['movedTo'].present?
@uri = @json['id']
@username = @json['preferredUsername']
@domain = Addressable::URI.parse(@uri).normalized_host
check_webfinger! unless only_key
ActivityPub::ProcessAccountService.new.call(@username, @domain, @json, only_key: only_key, verified_webfinger: !only_key)
rescue Error => e
Rails.logger.debug "Fetching actor #{uri} failed: #{e.message}"
raise unless suppress_errors
end
private
def check_webfinger!
webfinger = webfinger!("acct:#{@username}@#{@domain}")
confirmed_username, confirmed_domain = split_acct(webfinger.subject)
if @username.casecmp(confirmed_username).zero? && @domain.casecmp(confirmed_domain).zero?
raise Error, "Webfinger response for #{@username}@#{@domain} does not loop back to #{@uri}" if webfinger.link('self', 'href') != @uri
return
end
webfinger = webfinger!("acct:#{confirmed_username}@#{confirmed_domain}")
@username, @domain = split_acct(webfinger.subject)
unless confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
raise Webfinger::RedirectError, "Too many webfinger redirects for URI #{uri} (stopped at #{@username}@#{@domain})"
end
raise Error, "Webfinger response for #{@username}@#{@domain} does not loop back to #{@uri}" if webfinger.link('self', 'href') != @uri
rescue Webfinger::RedirectError => e
raise Error, e.message
rescue Webfinger::Error => e
raise Error, "Webfinger error when resolving #{@username}@#{@domain}: #{e.message}"
end
def split_acct(acct)
acct.gsub(/\Aacct:/, '').split('@')
end
def supported_context?
super(@json)
end
def expected_type?
equals_or_includes_any?(@json['type'], SUPPORTED_TYPES)
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment