Gab Social. All are welcome.

This commit is contained in:
robcolbert
2019-07-02 03:10:25 -04:00
commit bd0b5afc92
5366 changed files with 222812 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
require 'rails_helper'
RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do
subject { ActivityPub::FetchRemoteAccountService.new }
let!(:actor) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'https://example.com/alice',
type: 'Person',
preferredUsername: 'alice',
name: 'Alice',
summary: 'Foo bar',
inbox: 'http://example.com/alice/inbox',
}
end
describe '#call' do
let(:account) { subject.call('https://example.com/alice', id: true) }
shared_examples 'sets profile data' do
it 'returns an account' do
expect(account).to be_an Account
end
it 'sets display name' do
expect(account.display_name).to eq 'Alice'
end
it 'sets note' do
expect(account.note).to eq 'Foo bar'
end
it 'sets URL' do
expect(account.url).to eq 'https://example.com/alice'
end
end
context 'when the account does not have a inbox' do
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
before do
actor[:inbox] = nil
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
end
it 'fetches resource' do
account
expect(a_request(:get, 'https://example.com/alice')).to have_been_made.once
end
it 'looks up webfinger' do
account
expect(a_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com')).to have_been_made.once
end
it 'returns nil' do
expect(account).to be_nil
end
end
context 'when URI and WebFinger share the same host' do
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
before do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
end
it 'fetches resource' do
account
expect(a_request(:get, 'https://example.com/alice')).to have_been_made.once
end
it 'looks up webfinger' do
account
expect(a_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com')).to have_been_made.once
end
it 'sets username and domain from webfinger' do
expect(account.username).to eq 'alice'
expect(account.domain).to eq 'example.com'
end
include_examples 'sets profile data'
end
context 'when WebFinger presents different domain than URI' do
let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
before do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
end
it 'fetches resource' do
account
expect(a_request(:get, 'https://example.com/alice')).to have_been_made.once
end
it 'looks up webfinger' do
account
expect(a_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com')).to have_been_made.once
end
it 'looks up "redirected" webfinger' do
account
expect(a_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af')).to have_been_made.once
end
it 'sets username and domain from final webfinger' do
expect(account.username).to eq 'alice'
expect(account.domain).to eq 'iscool.af'
end
include_examples 'sets profile data'
end
context 'with wrong id' do
it 'does not create account' do
expect(subject.call('https://fake.address/@foo', prefetched_body: Oj.dump(actor))).to be_nil
end
end
end
end

View File

@@ -0,0 +1,96 @@
require 'rails_helper'
RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do
include ActionView::Helpers::TextHelper
let(:sender) { Fabricate(:account) }
let(:recipient) { Fabricate(:account) }
let(:valid_domain) { Rails.configuration.x.local_domain }
let(:note) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: "https://#{valid_domain}/@foo/1234",
type: 'Note',
content: 'Lorem ipsum',
attributedTo: ActivityPub::TagManager.instance.uri_for(sender),
}
end
subject { described_class.new }
describe '#call' do
before do
sender.update(uri: ActivityPub::TagManager.instance.uri_for(sender))
stub_request(:head, 'https://example.com/watch?v=12345').to_return(status: 404, body: '')
subject.call(object[:id], prefetched_body: Oj.dump(object))
end
context 'with Note object' do
let(:object) { note }
it 'creates status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.text).to eq 'Lorem ipsum'
end
end
context 'with Video object' do
let(:object) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: "https://#{valid_domain}/@foo/1234",
type: 'Video',
name: 'Nyan Cat 10 hours remix',
attributedTo: ActivityPub::TagManager.instance.uri_for(sender),
url: [
{
type: 'Link',
mimeType: 'application/x-bittorrent',
href: "https://#{valid_domain}/12345.torrent",
},
{
type: 'Link',
mimeType: 'text/html',
href: "https://#{valid_domain}/watch?v=12345",
},
],
}
end
it 'creates status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.url).to eq "https://#{valid_domain}/watch?v=12345"
expect(strip_tags(status.text)).to eq "Nyan Cat 10 hours remix https://#{valid_domain}/watch?v=12345"
end
end
context 'with wrong id' do
let(:note) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: "https://real.address/@foo/1234",
type: 'Note',
content: 'Lorem ipsum',
attributedTo: ActivityPub::TagManager.instance.uri_for(sender),
}
end
let(:object) do
temp = note.dup
temp[:id] = 'https://fake.address/@foo/5678'
temp
end
it 'does not create status' do
expect(sender.statuses.first).to be_nil
end
end
end
end

View File

@@ -0,0 +1,122 @@
require 'rails_helper'
RSpec.describe ActivityPub::FetchRepliesService, type: :service do
let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account') }
let(:status) { Fabricate(:status, account: actor) }
let(:collection_uri) { 'http://example.com/replies/1' }
let(:items) do
[
'http://example.com/self-reply-1',
'http://example.com/self-reply-2',
'http://example.com/self-reply-3',
'http://other.com/other-reply-1',
'http://other.com/other-reply-2',
'http://other.com/other-reply-3',
'http://example.com/self-reply-4',
'http://example.com/self-reply-5',
'http://example.com/self-reply-6',
]
end
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'Collection',
id: collection_uri,
items: items,
}.with_indifferent_access
end
subject { described_class.new }
describe '#call' do
context 'when the payload is a Collection with inlined replies' do
context 'when passing the collection itself' do
it 'spawns workers for up to 5 replies on the same server' do
allow(FetchReplyWorker).to receive(:push_bulk)
subject.call(status, payload)
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
end
end
context 'when passing the URL to the collection' do
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
end
it 'spawns workers for up to 5 replies on the same server' do
allow(FetchReplyWorker).to receive(:push_bulk)
subject.call(status, collection_uri)
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
end
end
end
context 'when the payload is an OrderedCollection with inlined replies' do
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'OrderedCollection',
id: collection_uri,
orderedItems: items,
}.with_indifferent_access
end
context 'when passing the collection itself' do
it 'spawns workers for up to 5 replies on the same server' do
allow(FetchReplyWorker).to receive(:push_bulk)
subject.call(status, payload)
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
end
end
context 'when passing the URL to the collection' do
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
end
it 'spawns workers for up to 5 replies on the same server' do
allow(FetchReplyWorker).to receive(:push_bulk)
subject.call(status, collection_uri)
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
end
end
end
context 'when the payload is a paginated Collection with inlined replies' do
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'Collection',
id: collection_uri,
first: {
type: 'CollectionPage',
partOf: collection_uri,
items: items,
}
}.with_indifferent_access
end
context 'when passing the collection itself' do
it 'spawns workers for up to 5 replies on the same server' do
allow(FetchReplyWorker).to receive(:push_bulk)
subject.call(status, payload)
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
end
end
context 'when passing the URL to the collection' do
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
end
it 'spawns workers for up to 5 replies on the same server' do
allow(FetchReplyWorker).to receive(:push_bulk)
subject.call(status, collection_uri)
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
end
end
end
end
end

View File

@@ -0,0 +1,76 @@
require 'rails_helper'
RSpec.describe ActivityPub::ProcessAccountService, type: :service do
subject { described_class.new }
context 'property values' do
let(:payload) do
{
id: 'https://foo.test',
type: 'Actor',
inbox: 'https://foo.test/inbox',
attachment: [
{ type: 'PropertyValue', name: 'Pronouns', value: 'They/them' },
{ type: 'PropertyValue', name: 'Occupation', value: 'Unit test' },
],
}.with_indifferent_access
end
it 'parses out of attachment' do
account = subject.call('alice', 'example.com', payload)
expect(account.fields).to be_a Array
expect(account.fields.size).to eq 2
expect(account.fields[0]).to be_a Account::Field
expect(account.fields[0].name).to eq 'Pronouns'
expect(account.fields[0].value).to eq 'They/them'
expect(account.fields[1]).to be_a Account::Field
expect(account.fields[1].name).to eq 'Occupation'
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
allow(ProofProvider::Keybase::Worker).to receive(:perform_async)
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
allow(ProofProvider::Keybase::Worker).to receive(:perform_async)
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

View File

@@ -0,0 +1,55 @@
require 'rails_helper'
RSpec.describe ActivityPub::ProcessCollectionService, type: :service do
let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account') }
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'foo',
type: 'Create',
actor: ActivityPub::TagManager.instance.uri_for(actor),
object: {
id: 'bar',
type: 'Note',
content: 'Lorem ipsum',
},
}
end
let(:json) { Oj.dump(payload) }
subject { described_class.new }
describe '#call' do
context 'when actor is the sender'
context 'when actor differs from sender' do
let(:forwarder) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/other_account') }
it 'does not process payload if no signature exists' do
expect_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_account!).and_return(nil)
expect(ActivityPub::Activity).not_to receive(:factory)
subject.call(json, forwarder)
end
it 'processes payload with actor if valid signature exists' do
payload['signature'] = { 'type' => 'RsaSignature2017' }
expect_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_account!).and_return(actor)
expect(ActivityPub::Activity).to receive(:factory).with(instance_of(Hash), actor, instance_of(Hash))
subject.call(json, forwarder)
end
it 'does not process payload if invalid signature exists' do
payload['signature'] = { 'type' => 'RsaSignature2017' }
expect_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_account!).and_return(nil)
expect(ActivityPub::Activity).not_to receive(:factory)
subject.call(json, forwarder)
end
end
end
end