about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2019-03-30 02:12:06 +0100
committerGitHub <noreply@github.com>2019-03-30 02:12:06 +0100
commit1714ea597866556ef9dd21d5d382f1d9181e0924 (patch)
tree4cf231f724d1918790b31d53dbd5d1c8a2c80527
parent8fb69f1366577483f048bf4d69f610087da99cb1 (diff)
Add ActivityPub representation for identity proofs (#10414)
* Add ActivityPub representation for identity proofs

* Add tests
-rw-r--r--app/lib/activitypub/adapter.rb1
-rw-r--r--app/lib/proof_provider/keybase.rb3
-rw-r--r--app/serializers/activitypub/actor_serializer.rb24
-rw-r--r--app/services/activitypub/process_account_service.rb28
-rw-r--r--spec/services/activitypub/process_account_service_spec.rb41
5 files changed, 93 insertions, 4 deletions
diff --git a/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb
index 7e0b16c25..94eb2899c 100644
--- a/app/lib/activitypub/adapter.rb
+++ b/app/lib/activitypub/adapter.rb
@@ -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
diff --git a/app/lib/proof_provider/keybase.rb b/app/lib/proof_provider/keybase.rb
index 672e1cb4b..628972e9d 100644
--- a/app/lib/proof_provider/keybase.rb
+++ b/app/lib/proof_provider/keybase.rb
@@ -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
diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb
index 4b982b955..0644219fb 100644
--- a/app/serializers/activitypub/actor_serializer.rb
+++ b/app/serializers/activitypub/actor_serializer.rb
@@ -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
diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb
index 5e3308428..6d0609ca0 100644
--- a/app/services/activitypub/process_account_service.rb
+++ b/app/services/activitypub/process_account_service.rb
@@ -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
diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb
index d3318b2ed..ab8fb5cc3 100644
--- a/spec/services/activitypub/process_account_service_spec.rb
+++ b/spec/services/activitypub/process_account_service_spec.rb
@@ -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