2017-08-08 19:52:15 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
class ActivityPub::ProcessAccountService < BaseService
|
|
|
|
include JsonLdHelper
|
|
|
|
|
|
|
|
# Should be called with confirmed valid JSON
|
|
|
|
# and WebFinger-resolved username and domain
|
|
|
|
def call(username, domain, json)
|
2017-09-04 16:26:33 +00:00
|
|
|
return if json['inbox'].blank?
|
2017-08-22 16:30:15 +00:00
|
|
|
|
2017-09-08 10:00:17 +00:00
|
|
|
@json = json
|
|
|
|
@uri = @json['id']
|
|
|
|
@username = username
|
|
|
|
@domain = domain
|
|
|
|
@account = Account.find_by(uri: @uri)
|
|
|
|
@collections = {}
|
2017-08-08 19:52:15 +00:00
|
|
|
|
2017-08-20 23:14:40 +00:00
|
|
|
create_account if @account.nil?
|
|
|
|
upgrade_account if @account.ostatus?
|
[WiP] Whenever a remote keypair changes, unfollow them and re-subscribe to … (#4907)
* Whenever a remote keypair changes, unfollow them and re-subscribe to them
In Mastodon (it could be different for other OStatus or AP-enabled software),
a keypair change is indicative of whole user (or instance) data loss. In this
situation, the “new” user might be different, and almost certainly has an empty
followers list. In this case, Mastodon instances will disagree on follower
lists, leading to unreliable delivery and “shadow followers”, that is users
believed by a remote instance to be followers, without the affected user
knowing.
Drawbacks of this change are:
1. If an user legitimately changes public key for some reason without losing
data (not possible in Mastodon at the moment), they will have their remote
followers unsubscribed/re-subscribed needlessly.
2. Depending of the number of remote followers, this may generate quite some
traffic.
3. If the user change is an attempt at usurpation, the remote followers will
unknowingly follow the usurper. Note that this is *not* a change of
behavior, Mastodon already behaves like that, although delivery might be
unreliable, and the usurper would not have known the former user's
followers.
* Rename ResubscribeWorker to RefollowWorker
* Process followers in batches
2017-09-12 21:10:40 +00:00
|
|
|
old_public_key = @account.public_key
|
2017-08-08 19:52:15 +00:00
|
|
|
update_account
|
[WiP] Whenever a remote keypair changes, unfollow them and re-subscribe to … (#4907)
* Whenever a remote keypair changes, unfollow them and re-subscribe to them
In Mastodon (it could be different for other OStatus or AP-enabled software),
a keypair change is indicative of whole user (or instance) data loss. In this
situation, the “new” user might be different, and almost certainly has an empty
followers list. In this case, Mastodon instances will disagree on follower
lists, leading to unreliable delivery and “shadow followers”, that is users
believed by a remote instance to be followers, without the affected user
knowing.
Drawbacks of this change are:
1. If an user legitimately changes public key for some reason without losing
data (not possible in Mastodon at the moment), they will have their remote
followers unsubscribed/re-subscribed needlessly.
2. Depending of the number of remote followers, this may generate quite some
traffic.
3. If the user change is an attempt at usurpation, the remote followers will
unknowingly follow the usurper. Note that this is *not* a change of
behavior, Mastodon already behaves like that, although delivery might be
unreliable, and the usurper would not have known the former user's
followers.
* Rename ResubscribeWorker to RefollowWorker
* Process followers in batches
2017-09-12 21:10:40 +00:00
|
|
|
RefollowWorker.perform_async(@account.id) if old_public_key != @account.public_key
|
2017-08-08 19:52:15 +00:00
|
|
|
|
|
|
|
@account
|
|
|
|
rescue Oj::ParseError
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def create_account
|
|
|
|
@account = Account.new
|
2017-08-20 23:14:40 +00:00
|
|
|
@account.protocol = :activitypub
|
2017-08-08 19:52:15 +00:00
|
|
|
@account.username = @username
|
|
|
|
@account.domain = @domain
|
|
|
|
@account.uri = @uri
|
|
|
|
@account.suspended = true if auto_suspend?
|
|
|
|
@account.silenced = true if auto_silence?
|
|
|
|
@account.private_key = nil
|
|
|
|
@account.save!
|
|
|
|
end
|
|
|
|
|
|
|
|
def update_account
|
|
|
|
@account.last_webfingered_at = Time.now.utc
|
|
|
|
@account.protocol = :activitypub
|
|
|
|
@account.inbox_url = @json['inbox'] || ''
|
|
|
|
@account.outbox_url = @json['outbox'] || ''
|
2017-09-04 16:26:33 +00:00
|
|
|
@account.shared_inbox_url = (@json['endpoints'].is_a?(Hash) ? @json['endpoints']['sharedInbox'] : @json['sharedInbox']) || ''
|
2017-08-08 19:52:15 +00:00
|
|
|
@account.followers_url = @json['followers'] || ''
|
2017-09-04 16:26:33 +00:00
|
|
|
@account.url = url || @uri
|
2017-08-08 19:52:15 +00:00
|
|
|
@account.display_name = @json['name'] || ''
|
|
|
|
@account.note = @json['summary'] || ''
|
2017-09-09 11:41:45 +00:00
|
|
|
@account.avatar_remote_url = image_url('icon') unless skip_download?
|
|
|
|
@account.header_remote_url = image_url('image') unless skip_download?
|
2017-08-08 19:52:15 +00:00
|
|
|
@account.public_key = public_key || ''
|
2017-09-02 21:13:35 +00:00
|
|
|
@account.locked = @json['manuallyApprovesFollowers'] || false
|
2017-09-08 10:00:17 +00:00
|
|
|
@account.statuses_count = outbox_total_items if outbox_total_items.present?
|
|
|
|
@account.following_count = following_total_items if following_total_items.present?
|
|
|
|
@account.followers_count = followers_total_items if followers_total_items.present?
|
2017-09-09 11:41:45 +00:00
|
|
|
@account.save_with_optional_media!
|
2017-08-08 19:52:15 +00:00
|
|
|
end
|
|
|
|
|
2017-08-20 23:14:40 +00:00
|
|
|
def upgrade_account
|
|
|
|
ActivityPub::PostUpgradeWorker.perform_async(@account.domain)
|
|
|
|
end
|
|
|
|
|
2017-08-08 19:52:15 +00:00
|
|
|
def image_url(key)
|
|
|
|
value = first_of_value(@json[key])
|
|
|
|
|
|
|
|
return if value.nil?
|
2017-09-04 16:26:33 +00:00
|
|
|
return value['url'] if value.is_a?(Hash)
|
2017-08-08 19:52:15 +00:00
|
|
|
|
|
|
|
image = fetch_resource(value)
|
|
|
|
image['url'] if image
|
|
|
|
end
|
|
|
|
|
|
|
|
def public_key
|
|
|
|
value = first_of_value(@json['publicKey'])
|
|
|
|
|
|
|
|
return if value.nil?
|
|
|
|
return value['publicKeyPem'] if value.is_a?(Hash)
|
|
|
|
|
|
|
|
key = fetch_resource(value)
|
|
|
|
key['publicKeyPem'] if key
|
|
|
|
end
|
|
|
|
|
2017-09-04 16:26:33 +00:00
|
|
|
def url
|
|
|
|
return if @json['url'].blank?
|
|
|
|
|
|
|
|
value = first_of_value(@json['url'])
|
|
|
|
|
|
|
|
return value if value.is_a?(String)
|
|
|
|
|
|
|
|
value['href']
|
|
|
|
end
|
|
|
|
|
2017-09-08 10:00:17 +00:00
|
|
|
def outbox_total_items
|
|
|
|
collection_total_items('outbox')
|
|
|
|
end
|
|
|
|
|
|
|
|
def following_total_items
|
|
|
|
collection_total_items('following')
|
|
|
|
end
|
|
|
|
|
|
|
|
def followers_total_items
|
|
|
|
collection_total_items('followers')
|
|
|
|
end
|
|
|
|
|
|
|
|
def collection_total_items(type)
|
|
|
|
return if @json[type].blank?
|
|
|
|
return @collections[type] if @collections.key?(type)
|
|
|
|
|
|
|
|
collection = fetch_resource(@json[type])
|
|
|
|
|
|
|
|
@collections[type] = collection.is_a?(Hash) && collection['totalItems'].present? && collection['totalItems'].is_a?(Numeric) ? collection['totalItems'] : nil
|
|
|
|
rescue HTTP::Error, OpenSSL::SSL::SSLError
|
|
|
|
@collections[type] = nil
|
|
|
|
end
|
|
|
|
|
2017-09-09 11:41:45 +00:00
|
|
|
def skip_download?
|
|
|
|
@account.suspended? || domain_block&.reject_media?
|
|
|
|
end
|
|
|
|
|
2017-08-08 19:52:15 +00:00
|
|
|
def auto_suspend?
|
|
|
|
domain_block && domain_block.suspend?
|
|
|
|
end
|
|
|
|
|
|
|
|
def auto_silence?
|
|
|
|
domain_block && domain_block.silence?
|
|
|
|
end
|
|
|
|
|
|
|
|
def domain_block
|
|
|
|
return @domain_block if defined?(@domain_block)
|
|
|
|
@domain_block = DomainBlock.find_by(domain: @domain)
|
|
|
|
end
|
|
|
|
end
|