* Add ActivityPub representation for identity proofs * Add testsmaster
@@ -18,6 +18,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base | |||
atom_uri: { 'ostatus' => 'http://ostatus.org#', 'atomUri' => 'ostatus:atomUri' }, | |||
conversation: { 'ostatus' => 'http://ostatus.org#', 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri', 'conversation' => 'ostatus:conversation' }, | |||
focal_point: { 'toot' => 'http://joinmastodon.org/ns#', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' } }, | |||
identity_proof: { 'toot' => 'http://joinmastodon.org/ns#', 'IdentityProof' => 'toot:IdentityProof' }, | |||
}.freeze | |||
def self.default_key_transform | |||
@@ -28,7 +28,8 @@ class ProofProvider::Keybase | |||
return | |||
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? | |||
@proof.verified = true | |||
@@ -6,7 +6,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer | |||
context :security | |||
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, | |||
:inbox, :outbox, :featured, | |||
@@ -115,7 +115,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer | |||
end | |||
def virtual_attachments | |||
object.fields | |||
object.fields + object.identity_proofs.active | |||
end | |||
def moved_to | |||
@@ -158,4 +158,24 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer | |||
Formatter.instance.format_field(object.account, object.value) | |||
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 |
@@ -24,6 +24,7 @@ class ActivityPub::ProcessAccountService < BaseService | |||
create_account if @account.nil? | |||
update_account | |||
process_tags | |||
process_attachments | |||
else | |||
raise Mastodon::RaceConditionError | |||
end | |||
@@ -151,7 +152,7 @@ class ActivityPub::ProcessAccountService < BaseService | |||
def property_values | |||
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 | |||
def mismatching_origin?(url) | |||
@@ -231,6 +232,23 @@ class ActivityPub::ProcessAccountService < BaseService | |||
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) | |||
return if skip_download? | |||
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.save | |||
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 |
@@ -28,4 +28,45 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do | |||
expect(account.fields[1].value).to eq 'Unit test' | |||
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 |