mastodon/spec/controllers/accounts_controller_spec.rb
ThibG aecdaf5a8c Do not serve account actors at all in limited federation mode (#14800)
* Do not serve account actors at all in limited federation mode

When an account is fetched without a signature from an allowed instance,
return an error.

This isn't really an improvement in security, as the only information that was
previously returned was required protocol-level info, and the only personal bit
was the existence of the account. The existence of the account can still be
checked by issuing a webfinger query, as those are accepted without signatures.

However, this change makes it so that unallowed instances won't create account
records on their end when they find a reference to an unknown account.

The previous behavior of rendering a limited list of fields, instead of not
rendering the actor at all, was in order to prevent situations in which two
instances in Authorized Fetch mode or Limited Federation mode would fail to
reach each other because resolving an account would require a signed query…
from an account which can only be fetched with a signed query itself. However,
this should now be fine as fetching accounts is done by signing on behalf of
the special instance actor, which does not require any kind of valid signature
to be fetched.

* Fix tests
2020-10-19 14:45:12 +02:00

603 lines
20 KiB
Ruby

require 'rails_helper'
RSpec.describe AccountsController, type: :controller do
render_views
let(:account) { Fabricate(:user).account }
shared_examples 'cachable response' do
it 'does not set cookies' do
expect(response.cookies).to be_empty
expect(response.headers['Set-Cookies']).to be nil
end
it 'does not set sessions' do
expect(session).to be_empty
end
it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end
end
describe 'GET #show' do
let(:format) { 'html' }
let!(:status) { Fabricate(:status, account: account) }
let!(:status_reply) { Fabricate(:status, account: account, thread: Fabricate(:status)) }
let!(:status_self_reply) { Fabricate(:status, account: account, thread: status) }
let!(:status_media) { Fabricate(:status, account: account) }
let!(:status_pinned) { Fabricate(:status, account: account) }
let!(:status_private) { Fabricate(:status, account: account, visibility: :private) }
let!(:status_direct) { Fabricate(:status, account: account, visibility: :direct) }
let!(:status_reblog) { Fabricate(:status, account: account, reblog: Fabricate(:status)) }
before do
status_media.media_attachments << Fabricate(:media_attachment, account: account, type: :image)
account.pinned_statuses << status_pinned
end
shared_examples 'preliminary checks' do
context 'when account is not approved' do
before do
account.user.update(approved: false)
end
it 'returns http not found' do
get :show, params: { username: account.username, format: format }
expect(response).to have_http_status(404)
end
end
context 'when account is suspended' do
before do
account.suspend!
end
it 'returns http gone' do
get :show, params: { username: account.username, format: format }
expect(response).to have_http_status(410)
end
end
end
context 'as HTML' do
let(:format) { 'html' }
it_behaves_like 'preliminary checks'
shared_examples 'common response characteristics' do
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns Link header' do
expect(response.headers['Link'].to_s).to include ActivityPub::TagManager.instance.uri_for(account)
end
it 'renders show template' do
expect(response).to render_template(:show)
end
end
context do
before do
get :show, params: { username: account.username, format: format }
end
it_behaves_like 'common response characteristics'
it 'renders public status' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
end
it 'renders self-reply' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
end
it 'renders status with media' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
end
it 'renders reblog' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
end
it 'renders pinned status' do
expect(response.body).to include(I18n.t('stream_entries.pinned'))
end
it 'does not render private status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
end
it 'does not render direct status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
end
it 'does not render reply to someone else' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
end
end
context 'when signed-in' do
let(:user) { Fabricate(:user) }
before do
sign_in(user)
end
context 'when user follows account' do
before do
user.account.follow!(account)
get :show, params: { username: account.username, format: format }
end
it 'does not render private status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
end
end
context 'when user is blocked' do
before do
account.block!(user.account)
get :show, params: { username: account.username, format: format }
end
it 'renders unavailable message' do
expect(response.body).to include(I18n.t('accounts.unavailable'))
end
it 'does not render public status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
end
it 'does not render self-reply' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
end
it 'does not render status with media' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media))
end
it 'does not render reblog' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
end
it 'does not render pinned status' do
expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
end
it 'does not render private status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
end
it 'does not render direct status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
end
it 'does not render reply to someone else' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
end
end
end
context 'with replies' do
before do
allow(controller).to receive(:replies_requested?).and_return(true)
get :show, params: { username: account.username, format: format }
end
it_behaves_like 'common response characteristics'
it 'renders public status' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
end
it 'renders self-reply' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
end
it 'renders status with media' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
end
it 'renders reblog' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
end
it 'does not render pinned status' do
expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
end
it 'does not render private status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
end
it 'does not render direct status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
end
it 'renders reply to someone else' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reply))
end
end
context 'with media' do
before do
allow(controller).to receive(:media_requested?).and_return(true)
get :show, params: { username: account.username, format: format }
end
it_behaves_like 'common response characteristics'
it 'does not render public status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
end
it 'does not render self-reply' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
end
it 'renders status with media' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
end
it 'does not render reblog' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
end
it 'does not render pinned status' do
expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
end
it 'does not render private status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
end
it 'does not render direct status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
end
it 'does not render reply to someone else' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
end
end
context 'with tag' do
let(:tag) { Fabricate(:tag) }
let!(:status_tag) { Fabricate(:status, account: account) }
before do
allow(controller).to receive(:tag_requested?).and_return(true)
status_tag.tags << tag
get :show, params: { username: account.username, format: format, tag: tag.to_param }
end
it_behaves_like 'common response characteristics'
it 'does not render public status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
end
it 'does not render self-reply' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
end
it 'does not render status with media' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media))
end
it 'does not render reblog' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
end
it 'does not render pinned status' do
expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
end
it 'does not render private status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
end
it 'does not render direct status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
end
it 'does not render reply to someone else' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
end
it 'renders status with tag' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_tag))
end
end
end
context 'as JSON' do
let(:authorized_fetch_mode) { false }
let(:format) { 'json' }
before do
allow(controller).to receive(:authorized_fetch_mode?).and_return(authorized_fetch_mode)
end
it_behaves_like 'preliminary checks'
context do
before do
get :show, params: { username: account.username, format: format }
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_behaves_like 'cachable response'
it 'renders account' do
json = body_as_json
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
end
context 'in authorized fetch mode' do
let(:authorized_fetch_mode) { true }
it 'returns http unauthorized' do
expect(response).to have_http_status(401)
end
end
end
context 'when signed in' do
let(:user) { Fabricate(:user) }
before do
sign_in(user)
get :show, params: { username: account.username, format: format }
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 public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end
it 'renders account' do
json = body_as_json
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
end
end
context 'with signature' do
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
before do
allow(controller).to receive(:signed_request_account).and_return(remote_account)
get :show, params: { username: account.username, format: format }
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_behaves_like 'cachable response'
it 'renders account' do
json = body_as_json
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
end
context 'in authorized fetch mode' do
let(:authorized_fetch_mode) { true }
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 private Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'private'
end
it 'returns Vary header with Signature' do
expect(response.headers['Vary']).to include 'Signature'
end
it 'renders account' do
json = body_as_json
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
end
end
end
end
context 'as RSS' do
let(:format) { 'rss' }
it_behaves_like 'preliminary checks'
shared_examples 'common response characteristics' do
it 'returns http success' do
expect(response).to have_http_status(200)
end
it_behaves_like 'cachable response'
end
context do
before do
get :show, params: { username: account.username, format: format }
end
it_behaves_like 'common response characteristics'
it 'renders public status' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
end
it 'renders self-reply' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
end
it 'renders status with media' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
end
it 'does not render reblog' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
end
it 'does not render private status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
end
it 'does not render direct status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
end
it 'does not render reply to someone else' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
end
end
context 'with replies' do
before do
allow(controller).to receive(:replies_requested?).and_return(true)
get :show, params: { username: account.username, format: format }
end
it_behaves_like 'common response characteristics'
it 'renders public status' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
end
it 'renders self-reply' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
end
it 'renders status with media' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
end
it 'does not render reblog' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
end
it 'does not render private status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
end
it 'does not render direct status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
end
it 'renders reply to someone else' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reply))
end
end
context 'with media' do
before do
allow(controller).to receive(:media_requested?).and_return(true)
get :show, params: { username: account.username, format: format }
end
it_behaves_like 'common response characteristics'
it 'does not render public status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
end
it 'does not render self-reply' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
end
it 'renders status with media' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
end
it 'does not render reblog' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
end
it 'does not render private status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
end
it 'does not render direct status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
end
it 'does not render reply to someone else' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
end
end
context 'with tag' do
let(:tag) { Fabricate(:tag) }
let!(:status_tag) { Fabricate(:status, account: account) }
before do
allow(controller).to receive(:tag_requested?).and_return(true)
status_tag.tags << tag
get :show, params: { username: account.username, format: format, tag: tag.to_param }
end
it_behaves_like 'common response characteristics'
it 'does not render public status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
end
it 'does not render self-reply' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
end
it 'does not render status with media' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media))
end
it 'does not render reblog' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
end
it 'does not render private status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
end
it 'does not render direct status' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
end
it 'does not render reply to someone else' do
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
end
it 'renders status with tag' do
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_tag))
end
end
end
end
end