forked from asonix/mastodon
* 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
604 lines
20 KiB
Ruby
604 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
|