From 60ba6d1c826d5d0ae514393788e0466bcb335e0b Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 12 Jun 2024 11:44:11 +0200 Subject: [PATCH] Add webfinger confirmation to username update scenario --- app/lib/activitypub/activity/update.rb | 24 +++++++--- spec/lib/activitypub/activity/update_spec.rb | 50 ++++++++++++++++++++ 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/app/lib/activitypub/activity/update.rb b/app/lib/activitypub/activity/update.rb index 1793b4137ba..b132258aec3 100644 --- a/app/lib/activitypub/activity/update.rb +++ b/app/lib/activitypub/activity/update.rb @@ -25,12 +25,7 @@ class ActivityPub::Activity::Update < ActivityPub::Activity request_id: @options[:request_id], } - if @account.username != @object['preferredUsername'] - account_proxy = @account.dup - account_proxy.username = @object['preferredUsername'] - UniqueUsernameValidator.new.validate(account_proxy) - opts[:allow_username_update] = account_proxy.errors.blank? - end + opts[:allow_username_update] = allow_username_update? if @account.username != @object['preferredUsername'] ActivityPub::ProcessAccountService.new.call(@account.username, @account.domain, @object, opts) end @@ -44,4 +39,21 @@ class ActivityPub::Activity::Update < ActivityPub::Activity ActivityPub::ProcessStatusUpdateService.new.call(@status, @json, @object, request_id: @options[:request_id]) end + + def allow_username_update? + updated_username_unique? && updated_username_confirmed? + end + + def updated_username_unique? + account_proxy = @account.dup + account_proxy.username = @object['preferredUsername'] + UniqueUsernameValidator.new.validate(account_proxy) + account_proxy.errors.blank? + end + + def updated_username_confirmed? + webfinger = Webfinger.new("acct:#{@object['preferredUsername']}@#{@account.domain}").perform + confirmed_username, confirmed_domain = webfinger.subject.delete_prefix('acct:').split('@') + confirmed_username == @object['preferredUsername'] && confirmed_domain == @account.domain + end end diff --git a/spec/lib/activitypub/activity/update_spec.rb b/spec/lib/activitypub/activity/update_spec.rb index 5393e69bd9b..716c4d7363f 100644 --- a/spec/lib/activitypub/activity/update_spec.rb +++ b/spec/lib/activitypub/activity/update_spec.rb @@ -64,7 +64,9 @@ RSpec.describe ActivityPub::Activity::Update do context 'when Actor username changes' do let!(:original_username) { sender.username } + let!(:original_handle) { "#{original_username}@#{sender.domain}" } let!(:updated_username) { 'updated_username' } + let!(:updated_handle) { "#{updated_username}@#{sender.domain}" } let(:updated_username_json) { actor_json.merge(preferredUsername: updated_username) } let(:json) do { @@ -75,6 +77,29 @@ RSpec.describe ActivityPub::Activity::Update do object: updated_username_json, }.with_indifferent_access end + let(:webfinger_response) do + { + subject: "acct:#{updated_handle}", + links: [ + { + rel: 'self', + type: 'application/activity+json', + href: sender.uri, + }, + ], + } + end + + before do + stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:#{updated_handle}") + .to_return( + body: webfinger_response.to_json, + headers: { + 'Content-Type' => 'application/json', + }, + status: 200 + ) + end it 'updates profile' do subject.perform @@ -105,6 +130,31 @@ RSpec.describe ActivityPub::Activity::Update do expect(sender.reload.username).to eq original_username end end + + context 'when updated username is not confirmed via webfinger' do + let(:webfinger_response) do + { + subject: "acct:#{original_handle}", + links: [ + { + rel: 'self', + type: 'application/activity+json', + href: sender.uri, + }, + ], + } + end + + it 'updates profile' do + subject.perform + expect(sender.reload.display_name).to eq 'Totally modified now' + end + + it 'does not update username' do + subject.perform + expect(sender.reload.username).to eq original_username + end + end end end