Add ActivityPub representation for identity proofs (#10414)
* Add ActivityPub representation for identity proofs * Add tests
This commit is contained in:
parent
8fb69f1366
commit
1714ea5978
|
@ -18,6 +18,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
|
||||||
atom_uri: { 'ostatus' => 'http://ostatus.org#', 'atomUri' => 'ostatus:atomUri' },
|
atom_uri: { 'ostatus' => 'http://ostatus.org#', 'atomUri' => 'ostatus:atomUri' },
|
||||||
conversation: { 'ostatus' => 'http://ostatus.org#', 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri', 'conversation' => 'ostatus:conversation' },
|
conversation: { 'ostatus' => 'http://ostatus.org#', 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri', 'conversation' => 'ostatus:conversation' },
|
||||||
focal_point: { 'toot' => 'http://joinmastodon.org/ns#', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' } },
|
focal_point: { 'toot' => 'http://joinmastodon.org/ns#', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' } },
|
||||||
|
identity_proof: { 'toot' => 'http://joinmastodon.org/ns#', 'IdentityProof' => 'toot:IdentityProof' },
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
def self.default_key_transform
|
def self.default_key_transform
|
||||||
|
|
|
@ -28,7 +28,8 @@ class ProofProvider::Keybase
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
return if @proof.provider_username.blank?
|
# Do not perform synchronous validation for remote accounts
|
||||||
|
return if @proof.provider_username.blank? || !@proof.account.local?
|
||||||
|
|
||||||
if verifier.valid?
|
if verifier.valid?
|
||||||
@proof.verified = true
|
@proof.verified = true
|
||||||
|
|
|
@ -6,7 +6,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
||||||
context :security
|
context :security
|
||||||
|
|
||||||
context_extensions :manually_approves_followers, :featured, :also_known_as,
|
context_extensions :manually_approves_followers, :featured, :also_known_as,
|
||||||
:moved_to, :property_value, :hashtag, :emoji
|
:moved_to, :property_value, :hashtag, :emoji, :identity_proof
|
||||||
|
|
||||||
attributes :id, :type, :following, :followers,
|
attributes :id, :type, :following, :followers,
|
||||||
:inbox, :outbox, :featured,
|
:inbox, :outbox, :featured,
|
||||||
|
@ -115,7 +115,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
||||||
end
|
end
|
||||||
|
|
||||||
def virtual_attachments
|
def virtual_attachments
|
||||||
object.fields
|
object.fields + object.identity_proofs.active
|
||||||
end
|
end
|
||||||
|
|
||||||
def moved_to
|
def moved_to
|
||||||
|
@ -158,4 +158,24 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
||||||
Formatter.instance.format_field(object.account, object.value)
|
Formatter.instance.format_field(object.account, object.value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class AccountIdentityProofSerializer < ActivityPub::Serializer
|
||||||
|
attributes :type, :name, :signature_algorithm, :signature_value
|
||||||
|
|
||||||
|
def type
|
||||||
|
'IdentityProof'
|
||||||
|
end
|
||||||
|
|
||||||
|
def name
|
||||||
|
object.provider_username
|
||||||
|
end
|
||||||
|
|
||||||
|
def signature_algorithm
|
||||||
|
object.provider
|
||||||
|
end
|
||||||
|
|
||||||
|
def signature_value
|
||||||
|
object.token
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,6 +24,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||||
create_account if @account.nil?
|
create_account if @account.nil?
|
||||||
update_account
|
update_account
|
||||||
process_tags
|
process_tags
|
||||||
|
process_attachments
|
||||||
else
|
else
|
||||||
raise Mastodon::RaceConditionError
|
raise Mastodon::RaceConditionError
|
||||||
end
|
end
|
||||||
|
@ -151,7 +152,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||||
|
|
||||||
def property_values
|
def property_values
|
||||||
return unless @json['attachment'].is_a?(Array)
|
return unless @json['attachment'].is_a?(Array)
|
||||||
@json['attachment'].select { |attachment| attachment['type'] == 'PropertyValue' }.map { |attachment| attachment.slice('name', 'value') }
|
as_array(@json['attachment']).select { |attachment| attachment['type'] == 'PropertyValue' }.map { |attachment| attachment.slice('name', 'value') }
|
||||||
end
|
end
|
||||||
|
|
||||||
def mismatching_origin?(url)
|
def mismatching_origin?(url)
|
||||||
|
@ -231,6 +232,23 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def process_attachments
|
||||||
|
return if @json['attachment'].blank?
|
||||||
|
|
||||||
|
previous_proofs = @account.identity_proofs.to_a
|
||||||
|
current_proofs = []
|
||||||
|
|
||||||
|
as_array(@json['attachment']).each do |attachment|
|
||||||
|
next unless equals_or_includes?(attachment['type'], 'IdentityProof')
|
||||||
|
current_proofs << process_identity_proof(attachment)
|
||||||
|
end
|
||||||
|
|
||||||
|
previous_proofs.each do |previous_proof|
|
||||||
|
next if current_proofs.any? { |current_proof| current_proof.id == previous_proof.id }
|
||||||
|
previous_proof.delete
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def process_emoji(tag)
|
def process_emoji(tag)
|
||||||
return if skip_download?
|
return if skip_download?
|
||||||
return if tag['name'].blank? || tag['icon'].blank? || tag['icon']['url'].blank?
|
return if tag['name'].blank? || tag['icon'].blank? || tag['icon']['url'].blank?
|
||||||
|
@ -247,4 +265,12 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||||
emoji.image_remote_url = image_url
|
emoji.image_remote_url = image_url
|
||||||
emoji.save
|
emoji.save
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def process_identity_proof(attachment)
|
||||||
|
provider = attachment['signatureAlgorithm']
|
||||||
|
provider_username = attachment['name']
|
||||||
|
token = attachment['signatureValue']
|
||||||
|
|
||||||
|
@account.identity_proofs.where(provider: provider, provider_username: provider_username).find_or_create_by(provider: provider, provider_username: provider_username, token: token)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,4 +28,45 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
|
||||||
expect(account.fields[1].value).to eq 'Unit test'
|
expect(account.fields[1].value).to eq 'Unit test'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'identity proofs' do
|
||||||
|
let(:payload) do
|
||||||
|
{
|
||||||
|
id: 'https://foo.test',
|
||||||
|
type: 'Actor',
|
||||||
|
inbox: 'https://foo.test/inbox',
|
||||||
|
attachment: [
|
||||||
|
{ type: 'IdentityProof', name: 'Alice', signatureAlgorithm: 'keybase', signatureValue: 'a' * 66 },
|
||||||
|
],
|
||||||
|
}.with_indifferent_access
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'parses out of attachment' do
|
||||||
|
account = subject.call('alice', 'example.com', payload)
|
||||||
|
|
||||||
|
expect(account.identity_proofs.count).to eq 1
|
||||||
|
|
||||||
|
proof = account.identity_proofs.first
|
||||||
|
|
||||||
|
expect(proof.provider).to eq 'keybase'
|
||||||
|
expect(proof.provider_username).to eq 'Alice'
|
||||||
|
expect(proof.token).to eq 'a' * 66
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes no longer present proofs' do
|
||||||
|
account = Fabricate(:account, username: 'alice', domain: 'example.com')
|
||||||
|
old_proof = Fabricate(:account_identity_proof, account: account, provider: 'keybase', provider_username: 'Bob', token: 'b' * 66)
|
||||||
|
|
||||||
|
subject.call('alice', 'example.com', payload)
|
||||||
|
|
||||||
|
expect(account.identity_proofs.count).to eq 1
|
||||||
|
expect(account.identity_proofs.find_by(id: old_proof.id)).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'queues a validity check on the proof' do
|
||||||
|
allow(ProofProvider::Keybase::Worker).to receive(:perform_async)
|
||||||
|
account = subject.call('alice', 'example.com', payload)
|
||||||
|
expect(ProofProvider::Keybase::Worker).to have_received(:perform_async)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue