Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
- `.github/dependabot.yml`:
  Updated upstream, we deleted it to not be flooded by Depandabot.
  Kept deleted.
- `Gemfile.lock`:
  Puma updated on both sides, went for the most recent version.
- `app/controllers/api/v1/mutes_controller.rb`:
  Upstream updated the serializer to support timed mutes, while
  glitch-soc added a custom API ages ago to get information that
  is already available elsewhere.
  Dropped the glitch-soc-specific API, went with upstream changes.
- `app/javascript/core/admin.js`:
  Conflict due to changing how assets are loaded. Went with upstream.
- `app/javascript/packs/public.js`:
  Conflict due to changing how assets are loaded. Went with upstream.
- `app/models/mute.rb`:
  🤷
- `app/models/user.rb`:
  New user setting added upstream while we have glitch-soc-specific
  user settings. Added upstream's user setting.
- `config/settings.yml`:
  Upstream added a new user setting close to a user setting we had
  changed the defaults for. Added the new upstream setting.
- `package.json`:
  Upstream dependency updated “too close” to a glitch-soc-specific
  dependency. No real conflict. Updated the dependency.
This commit is contained in:
Thibaut Girka
2020-10-21 19:10:50 +02:00
131 changed files with 2527 additions and 791 deletions

View File

@@ -0,0 +1,58 @@
require 'rails_helper'
RSpec.describe ActivityPub::FollowersSynchronizationsController, type: :controller do
let!(:account) { Fabricate(:account) }
let!(:follower_1) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/a') }
let!(:follower_2) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/b') }
let!(:follower_3) { Fabricate(:account, domain: 'foo.com', uri: 'https://foo.com/users/a') }
before do
follower_1.follow!(account)
follower_2.follow!(account)
follower_3.follow!(account)
end
before do
allow(controller).to receive(:signed_request_account).and_return(remote_account)
end
describe 'GET #show' do
context 'without signature' do
let(:remote_account) { nil }
before do
get :show, params: { account_username: account.username }
end
it 'returns http not authorized' do
expect(response).to have_http_status(401)
end
end
context 'with signature from example.com' do
let(:remote_account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/instance') }
before do
get :show, params: { account_username: account.username }
end
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns application/activity+json' do
expect(response.content_type).to eq 'application/activity+json'
end
it 'returns orderedItems with followers from example.com' do
json = body_as_json
expect(json[:orderedItems]).to be_an Array
expect(json[:orderedItems].sort).to eq [follower_1.uri, follower_2.uri]
end
it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to eq 'max-age=0, private'
end
end
end
end

View File

@@ -22,6 +22,56 @@ RSpec.describe ActivityPub::InboxesController, type: :controller do
end
end
context 'with Collection-Synchronization header' do
let(:remote_account) { Fabricate(:account, followers_url: 'https://example.com/followers', domain: 'example.com', uri: 'https://example.com/actor', protocol: :activitypub) }
let(:synchronization_collection) { remote_account.followers_url }
let(:synchronization_url) { 'https://example.com/followers-for-domain' }
let(:synchronization_hash) { 'somehash' }
let(:synchronization_header) { "collectionId=\"#{synchronization_collection}\", digest=\"#{synchronization_hash}\", url=\"#{synchronization_url}\"" }
before do
allow(ActivityPub::FollowersSynchronizationWorker).to receive(:perform_async).and_return(nil)
allow_any_instance_of(Account).to receive(:local_followers_hash).and_return('somehash')
request.headers['Collection-Synchronization'] = synchronization_header
post :create, body: '{}'
end
context 'with mismatching target collection' do
let(:synchronization_collection) { 'https://example.com/followers2' }
it 'does not start a synchronization job' do
expect(ActivityPub::FollowersSynchronizationWorker).not_to have_received(:perform_async)
end
end
context 'with mismatching domain in partial collection attribute' do
let(:synchronization_url) { 'https://example.org/followers' }
it 'does not start a synchronization job' do
expect(ActivityPub::FollowersSynchronizationWorker).not_to have_received(:perform_async)
end
end
context 'with matching digest' do
it 'does not start a synchronization job' do
expect(ActivityPub::FollowersSynchronizationWorker).not_to have_received(:perform_async)
end
end
context 'with mismatching digest' do
let(:synchronization_hash) { 'wronghash' }
it 'starts a synchronization job' do
expect(ActivityPub::FollowersSynchronizationWorker).to have_received(:perform_async)
end
end
it 'returns http accepted' do
expect(response).to have_http_status(202)
end
end
context 'without signature' do
before do
post :create, body: '{}'

View File

@@ -43,8 +43,7 @@ describe RemoteFollowController do
end
it 'renders new when template is nil' do
link_with_nil_template = double(template: nil)
resource_with_link = double(link: link_with_nil_template)
resource_with_link = double(link: nil)
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_link)
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
@@ -55,8 +54,7 @@ describe RemoteFollowController do
context 'when webfinger values are good' do
before do
link_with_template = double(template: 'http://example.com/follow_me?acct={uri}')
resource_with_link = double(link: link_with_template)
resource_with_link = double(link: 'http://example.com/follow_me?acct={uri}')
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_link)
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
end
@@ -78,8 +76,8 @@ describe RemoteFollowController do
expect(response).to render_template(:new)
end
it 'renders new with error when goldfinger fails' do
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_raise(Goldfinger::Error)
it 'renders new with error when webfinger fails' do
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_raise(Webfinger::Error)
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
expect(response).to render_template(:new)

View File

@@ -12,7 +12,7 @@ describe WellKnown::HostMetaController, type: :controller do
expect(response.body).to eq <<XML
<?xml version="1.0" encoding="UTF-8"?>
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
<Link rel="lrdd" type="application/xrd+xml" template="https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}"/>
<Link rel="lrdd" template="https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}"/>
</XRD>
XML
end

View File

@@ -0,0 +1,6 @@
Fabricator(:ip_block) do
ip ""
severity ""
expires_at "2020-10-08 22:20:37"
comment "MyText"
end

View File

@@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'rails_helper'
describe FastIpMap do
describe '#include?' do
subject { described_class.new([IPAddr.new('20.4.0.0/16'), IPAddr.new('145.22.30.0/24'), IPAddr.new('189.45.86.3')])}
it 'returns true for an exact match' do
expect(subject.include?(IPAddr.new('189.45.86.3'))).to be true
end
it 'returns true for a range match' do
expect(subject.include?(IPAddr.new('20.4.45.7'))).to be true
end
it 'returns false for no match' do
expect(subject.include?(IPAddr.new('145.22.40.64'))).to be false
end
end
end

View File

@@ -108,6 +108,7 @@ RSpec.describe FeedManager do
it 'returns false for status by followee mentioning another account' do
bob.follow!(alice)
jeff.follow!(alice)
status = PostStatusService.new.call(alice, text: 'Hey @jeff')
expect(FeedManager.instance.filter?(:home, status, bob)).to be false
end

View File

@@ -546,6 +546,49 @@ describe AccountInteractions do
end
end
describe '#followers_hash' do
let(:me) { Fabricate(:account, username: 'Me') }
let(:remote_1) { Fabricate(:account, username: 'alice', domain: 'example.org', uri: 'https://example.org/users/alice') }
let(:remote_2) { Fabricate(:account, username: 'bob', domain: 'example.org', uri: 'https://example.org/users/bob') }
let(:remote_3) { Fabricate(:account, username: 'eve', domain: 'foo.org', uri: 'https://foo.org/users/eve') }
before do
remote_1.follow!(me)
remote_2.follow!(me)
remote_3.follow!(me)
me.follow!(remote_1)
end
context 'on a local user' do
it 'returns correct hash for remote domains' do
expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec'
expect(me.remote_followers_hash('https://foo.org/')).to eq 'ccb9c18a67134cfff9d62c7f7e7eb88e6b803446c244b84265565f4eba29df0e'
end
it 'invalidates cache as needed when removing or adding followers' do
expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec'
remote_1.unfollow!(me)
expect(me.remote_followers_hash('https://example.org/')).to eq '241b00794ce9b46aa864f3220afadef128318da2659782985bac5ed5bd436bff'
remote_1.follow!(me)
expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec'
end
end
context 'on a remote user' do
it 'returns correct hash for remote domains' do
expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
end
it 'invalidates cache as needed when removing or adding followers' do
expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
me.unfollow!(remote_1)
expect(remote_1.local_followers_hash).to eq '0000000000000000000000000000000000000000000000000000000000000000'
me.follow!(remote_1)
expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
end
end
end
describe 'muting an account' do
let(:me) { Fabricate(:account, username: 'Me') }
let(:you) { Fabricate(:account, username: 'You') }

View File

@@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe IpBlock, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@@ -0,0 +1,105 @@
require 'rails_helper'
RSpec.describe ActivityPub::SynchronizeFollowersService, type: :service do
let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account', inbox_url: 'http://example.com/inbox') }
let(:alice) { Fabricate(:account, username: 'alice') }
let(:bob) { Fabricate(:account, username: 'bob') }
let(:eve) { Fabricate(:account, username: 'eve') }
let(:mallory) { Fabricate(:account, username: 'mallory') }
let(:collection_uri) { 'http://example.com/partial-followers' }
let(:items) do
[
ActivityPub::TagManager.instance.uri_for(alice),
ActivityPub::TagManager.instance.uri_for(eve),
ActivityPub::TagManager.instance.uri_for(mallory),
]
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 }
shared_examples 'synchronizes followers' do
before do
alice.follow!(actor)
bob.follow!(actor)
mallory.request_follow!(actor)
allow(ActivityPub::DeliveryWorker).to receive(:perform_async)
subject.call(actor, collection_uri)
end
it 'keeps expected followers' do
expect(alice.following?(actor)).to be true
end
it 'removes local followers not in the remote list' do
expect(bob.following?(actor)).to be false
end
it 'converts follow requests to follow relationships when they have been accepted' do
expect(mallory.following?(actor)).to be true
end
it 'sends an Undo Follow to the actor' do
expect(ActivityPub::DeliveryWorker).to have_received(:perform_async).with(anything, eve.id, actor.inbox_url)
end
end
describe '#call' do
context 'when the endpoint is a Collection of actor URIs' do
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
end
it_behaves_like 'synchronizes followers'
end
context 'when the endpoint is an OrderedCollection of actor URIs' do
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'OrderedCollection',
id: collection_uri,
orderedItems: items,
}.with_indifferent_access
end
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
end
it_behaves_like 'synchronizes followers'
end
context 'when the endpoint is a paginated Collection of actor URIs' 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
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
end
it_behaves_like 'synchronizes followers'
end
end
end

View File

@@ -3,6 +3,7 @@ require 'rails_helper'
RSpec.describe AppSignUpService, type: :service do
let(:app) { Fabricate(:application, scopes: 'read write') }
let(:good_params) { { username: 'alice', password: '12345678', email: 'good@email.com', agreement: true } }
let(:remote_ip) { IPAddr.new('198.0.2.1') }
subject { described_class.new }
@@ -10,16 +11,16 @@ RSpec.describe AppSignUpService, type: :service do
it 'returns nil when registrations are closed' do
tmp = Setting.registrations_mode
Setting.registrations_mode = 'none'
expect(subject.call(app, good_params)).to be_nil
expect(subject.call(app, remote_ip, good_params)).to be_nil
Setting.registrations_mode = tmp
end
it 'raises an error when params are missing' do
expect { subject.call(app, {}) }.to raise_error ActiveRecord::RecordInvalid
expect { subject.call(app, remote_ip, {}) }.to raise_error ActiveRecord::RecordInvalid
end
it 'creates an unconfirmed user with access token' do
access_token = subject.call(app, good_params)
access_token = subject.call(app, remote_ip, good_params)
expect(access_token).to_not be_nil
user = User.find_by(id: access_token.resource_owner_id)
expect(user).to_not be_nil
@@ -27,13 +28,13 @@ RSpec.describe AppSignUpService, type: :service do
end
it 'creates access token with the app\'s scopes' do
access_token = subject.call(app, good_params)
access_token = subject.call(app, remote_ip, good_params)
expect(access_token).to_not be_nil
expect(access_token.scopes.to_s).to eq 'read write'
end
it 'creates an account' do
access_token = subject.call(app, good_params)
access_token = subject.call(app, remote_ip, good_params)
expect(access_token).to_not be_nil
user = User.find_by(id: access_token.resource_owner_id)
expect(user).to_not be_nil
@@ -42,7 +43,7 @@ RSpec.describe AppSignUpService, type: :service do
end
it 'creates an account with invite request text' do
access_token = subject.call(app, good_params.merge(reason: 'Foo bar'))
access_token = subject.call(app, remote_ip, good_params.merge(reason: 'Foo bar'))
expect(access_token).to_not be_nil
user = User.find_by(id: access_token.resource_owner_id)
expect(user).to_not be_nil

View File

@@ -3,16 +3,22 @@
require 'rails_helper'
describe ActivityPub::DeliveryWorker do
include RoutingHelper
subject { described_class.new }
let(:sender) { Fabricate(:account) }
let(:payload) { 'test' }
before do
allow_any_instance_of(Account).to receive(:remote_followers_hash).with('https://example.com/').and_return('somehash')
end
describe 'perform' do
it 'performs a request' do
stub_request(:post, 'https://example.com/api').to_return(status: 200)
subject.perform(payload, sender.id, 'https://example.com/api')
expect(a_request(:post, 'https://example.com/api')).to have_been_made.once
subject.perform(payload, sender.id, 'https://example.com/api', { synchronize_followers: true })
expect(a_request(:post, 'https://example.com/api').with(headers: { 'Collection-Synchronization' => "collectionId=\"#{account_followers_url(sender)}\", digest=\"somehash\", url=\"#{account_followers_synchronization_url(sender)}\"" })).to have_been_made.once
end
it 'raises when request fails' do