Add ActivityPub representation for identity proofs (#10414)
* Add ActivityPub representation for identity proofs * Add tests
This commit is contained in:
		
							parent
							
								
									ed2862e249
								
							
						
					
					
						commit
						a82bc7f5ae
					
				
					 5 changed files with 93 additions and 4 deletions
				
			
		|  | @ -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 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue