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
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
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,
end
def virtual_attachments
- object.fields
+ object.fields + object.identity_proofs.active
end
def moved_to
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
create_account if @account.nil?
update_account
process_tags
+ process_attachments
else
raise Mastodon::RaceConditionError
end
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)
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?
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
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