Add initial support for ingesting and verifying remote quote posts (#34370)
This commit is contained in:
parent
a324edabdf
commit
df2611a10f
33 changed files with 1643 additions and 22 deletions
|
@ -7,7 +7,15 @@ RSpec.describe ActivityPub::Activity::Create do
|
|||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
{
|
||||
quote: {
|
||||
'@id': 'https://w3id.org/fep/044f#quote',
|
||||
'@type': '@id',
|
||||
},
|
||||
},
|
||||
],
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#foo'].join,
|
||||
type: 'Create',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
|
@ -879,6 +887,115 @@ RSpec.describe ActivityPub::Activity::Create do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with an unverifiable quote of a known post', feature: :inbound_quotes do
|
||||
let(:quoted_status) { Fabricate(:status) }
|
||||
|
||||
let(:object_json) do
|
||||
build_object(
|
||||
type: 'Note',
|
||||
content: 'woah what she said is amazing',
|
||||
quote: ActivityPub::TagManager.instance.uri_for(quoted_status)
|
||||
)
|
||||
end
|
||||
|
||||
it 'creates a status with an unverified quote' do
|
||||
expect { subject.perform }.to change(sender.statuses, :count).by(1)
|
||||
|
||||
status = sender.statuses.first
|
||||
expect(status).to_not be_nil
|
||||
expect(status.quote).to_not be_nil
|
||||
expect(status.quote).to have_attributes(
|
||||
state: 'pending',
|
||||
approval_uri: nil
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an unverifiable unknown post', feature: :inbound_quotes do
|
||||
let(:unknown_post_uri) { 'https://unavailable.example.com/unavailable-post' }
|
||||
|
||||
let(:object_json) do
|
||||
build_object(
|
||||
type: 'Note',
|
||||
content: 'woah what she said is amazing',
|
||||
quote: unknown_post_uri
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
stub_request(:get, unknown_post_uri).to_return(status: 404)
|
||||
end
|
||||
|
||||
it 'creates a status with an unverified quote' do
|
||||
expect { subject.perform }.to change(sender.statuses, :count).by(1)
|
||||
|
||||
status = sender.statuses.first
|
||||
expect(status).to_not be_nil
|
||||
expect(status.quote).to_not be_nil
|
||||
expect(status.quote).to have_attributes(
|
||||
state: 'pending',
|
||||
approval_uri: nil
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a verifiable quote of a known post', feature: :inbound_quotes do
|
||||
let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') }
|
||||
let(:quoted_status) { Fabricate(:status, account: quoted_account) }
|
||||
let(:approval_uri) { 'https://quoted.example.com/quote-approval' }
|
||||
|
||||
let(:object_json) do
|
||||
build_object(
|
||||
type: 'Note',
|
||||
content: 'woah what she said is amazing',
|
||||
quote: ActivityPub::TagManager.instance.uri_for(quoted_status),
|
||||
quoteAuthorization: approval_uri
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
stub_request(:get, approval_uri).to_return(headers: { 'Content-Type': 'application/activity+json' }, body: Oj.dump({
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
{
|
||||
toot: 'http://joinmastodon.org/ns#',
|
||||
QuoteAuthorization: 'toot:QuoteAuthorization',
|
||||
gts: 'https://gotosocial.org/ns#',
|
||||
interactionPolicy: {
|
||||
'@id': 'gts:interactionPolicy',
|
||||
'@type': '@id',
|
||||
},
|
||||
interactingObject: {
|
||||
'@id': 'gts:interactingObject',
|
||||
'@type': '@id',
|
||||
},
|
||||
interactionTarget: {
|
||||
'@id': 'gts:interactionTarget',
|
||||
'@type': '@id',
|
||||
},
|
||||
},
|
||||
],
|
||||
type: 'QuoteAuthorization',
|
||||
id: approval_uri,
|
||||
attributedTo: ActivityPub::TagManager.instance.uri_for(quoted_status.account),
|
||||
interactingObject: object_json[:id],
|
||||
interactionTarget: ActivityPub::TagManager.instance.uri_for(quoted_status),
|
||||
}))
|
||||
end
|
||||
|
||||
it 'creates a status with a verified quote' do
|
||||
expect { subject.perform }.to change(sender.statuses, :count).by(1)
|
||||
|
||||
status = sender.statuses.first
|
||||
expect(status).to_not be_nil
|
||||
expect(status.quote).to_not be_nil
|
||||
expect(status.quote).to have_attributes(
|
||||
state: 'accepted',
|
||||
approval_uri: approval_uri
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a vote to a local poll' do
|
||||
let(:poll) { Fabricate(:poll, options: %w(Yellow Blue)) }
|
||||
let!(:local_status) { Fabricate(:status, poll: poll) }
|
||||
|
|
|
@ -77,4 +77,61 @@ RSpec.describe ActivityPub::Activity::Delete do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the deleted object is an account' do
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Delete',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
signature: 'foo',
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
let(:service) { instance_double(DeleteAccountService, call: true) }
|
||||
|
||||
before do
|
||||
allow(DeleteAccountService).to receive(:new).and_return(service)
|
||||
end
|
||||
|
||||
it 'calls the account deletion service' do
|
||||
subject.perform
|
||||
|
||||
expect(service)
|
||||
.to have_received(:call).with(sender, { reserve_username: false, skip_activitypub: true })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the deleted object is a quote authorization' do
|
||||
let(:quoter) { Fabricate(:account, domain: 'b.example.com') }
|
||||
let(:status) { Fabricate(:status, account: quoter) }
|
||||
let(:quoted_status) { Fabricate(:status, account: sender, uri: 'https://example.com/statuses/1234') }
|
||||
let!(:quote) { Fabricate(:quote, approval_uri: 'https://example.com/approvals/1234', state: :accepted, status: status, quoted_status: quoted_status) }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Delete',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: quote.approval_uri,
|
||||
signature: 'foo',
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
it 'revokes the authorization' do
|
||||
expect { subject.perform }
|
||||
.to change { quote.reload.state }.to('revoked')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -40,10 +40,119 @@ RSpec.describe StatusCacheHydrator do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when handling an unapproved quote' do
|
||||
let(:quoted_status) { Fabricate(:status) }
|
||||
|
||||
before do
|
||||
Fabricate(:quote, status: status, quoted_status: quoted_status, state: :pending)
|
||||
end
|
||||
|
||||
it 'renders the same attributes as full render' do
|
||||
expect(subject).to eql(compare_to_hash)
|
||||
expect(subject[:quote]).to_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when handling an approved quote' do
|
||||
let(:quoted_status) { Fabricate(:status) }
|
||||
|
||||
before do
|
||||
Fabricate(:quote, status: status, quoted_status: quoted_status, state: :accepted)
|
||||
end
|
||||
|
||||
it 'renders the same attributes as full render' do
|
||||
expect(subject).to eql(compare_to_hash)
|
||||
expect(subject[:quote]).to_not be_nil
|
||||
end
|
||||
|
||||
context 'when the quoted post has been favourited' do
|
||||
before do
|
||||
FavouriteService.new.call(account, quoted_status)
|
||||
end
|
||||
|
||||
it 'renders the same attributes as full render' do
|
||||
expect(subject).to eql(compare_to_hash)
|
||||
expect(subject[:quote]).to_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the quoted post has been reblogged' do
|
||||
before do
|
||||
ReblogService.new.call(account, quoted_status)
|
||||
end
|
||||
|
||||
it 'renders the same attributes as full render' do
|
||||
expect(subject).to eql(compare_to_hash)
|
||||
expect(subject[:quote]).to_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the quoted post matches account filters' do
|
||||
let(:quoted_status) { Fabricate(:status, text: 'this toot is about that banned word') }
|
||||
|
||||
before do
|
||||
account.custom_filters.create!(phrase: 'filter1', context: %w(home), action: :hide, keywords_attributes: [{ keyword: 'banned' }, { keyword: 'irrelevant' }])
|
||||
end
|
||||
|
||||
it 'renders the same attributes as a full render' do
|
||||
expect(subject).to eql(compare_to_hash)
|
||||
expect(subject[:quote]).to_not be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when handling a reblog' do
|
||||
let(:reblog) { Fabricate(:status) }
|
||||
let(:status) { Fabricate(:status, reblog: reblog) }
|
||||
|
||||
context 'when the reblog has an approved quote' do
|
||||
let(:quoted_status) { Fabricate(:status) }
|
||||
|
||||
before do
|
||||
Fabricate(:quote, status: reblog, quoted_status: quoted_status, state: :accepted)
|
||||
end
|
||||
|
||||
it 'renders the same attributes as full render' do
|
||||
expect(subject).to eql(compare_to_hash)
|
||||
expect(subject[:reblog][:quote]).to_not be_nil
|
||||
end
|
||||
|
||||
context 'when the quoted post has been favourited' do
|
||||
before do
|
||||
FavouriteService.new.call(account, quoted_status)
|
||||
end
|
||||
|
||||
it 'renders the same attributes as full render' do
|
||||
expect(subject).to eql(compare_to_hash)
|
||||
expect(subject[:reblog][:quote]).to_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the quoted post has been reblogged' do
|
||||
before do
|
||||
ReblogService.new.call(account, quoted_status)
|
||||
end
|
||||
|
||||
it 'renders the same attributes as full render' do
|
||||
expect(subject).to eql(compare_to_hash)
|
||||
expect(subject[:reblog][:quote]).to_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the quoted post matches account filters' do
|
||||
let(:quoted_status) { Fabricate(:status, text: 'this toot is about that banned word') }
|
||||
|
||||
before do
|
||||
account.custom_filters.create!(phrase: 'filter1', context: %w(home), action: :hide, keywords_attributes: [{ keyword: 'banned' }, { keyword: 'irrelevant' }])
|
||||
end
|
||||
|
||||
it 'renders the same attributes as a full render' do
|
||||
expect(subject).to eql(compare_to_hash)
|
||||
expect(subject[:reblog][:quote]).to_not be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it has been favourited' do
|
||||
before do
|
||||
FavouriteService.new.call(account, reblog)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue