PAGE_SIZE = 20
include AccountControllerConcern
+ include SignatureAuthentication
before_action :set_cache_headers
before_action :set_body_classes
end
format.json do
- expires_in 3.minutes, public: true
- render json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter
+ expires_in 3.minutes, public: !(authorized_fetch_mode? && signed_request_account.present?)
+ render json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to
end
end
end
filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a
end
end
+
+ def restrict_fields_to
+ if signed_request_account.present? || public_fetch_mode?
+ # Return all fields
+ else
+ %i(id type preferred_username inbox public_key endpoints)
+ end
+ end
end
include SignatureVerification
include AccountOwnedConcern
+ before_action :require_signature!, if: :authorized_fetch_mode?
before_action :set_size
before_action :set_statuses
before_action :set_cache_headers
def show
- expires_in 3.minutes, public: true
+ expires_in 3.minutes, public: public_fetch_mode?
render json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, skip_activities: true
end
include JsonLdHelper
include AccountOwnedConcern
+ before_action :skip_unknown_actor_delete
+ before_action :require_signature!
+
def create
- if unknown_deleted_account?
- head 202
- elsif signed_request_account
- upgrade_account
- process_payload
- head 202
- else
- render plain: signature_verification_failure_reason, status: 401
- end
+ upgrade_account
+ process_payload
+ head 202
end
private
+ def skip_unknown_actor_delete
+ head 202 if unknown_deleted_account?
+ end
+
def unknown_deleted_account?
json = Oj.load(body, mode: :strict)
- json['type'] == 'Delete' && json['actor'].present? && json['actor'] == value_or_id(json['object']) && !Account.where(uri: json['actor']).exists?
+ json.is_a?(Hash) && json['type'] == 'Delete' && json['actor'].present? && json['actor'] == value_or_id(json['object']) && !Account.where(uri: json['actor']).exists?
rescue Oj::ParseError
false
end
def body
return @body if defined?(@body)
- @body = request.body.read.force_encoding('UTF-8')
+
+ @body = request.body.read
+ @body.force_encoding('UTF-8') if @body.present?
+
request.body.rewind if request.body.respond_to?(:rewind)
+
@body
end
include SignatureVerification
include AccountOwnedConcern
+ before_action :require_signature!, if: :authorized_fetch_mode?
before_action :set_statuses
before_action :set_cache_headers
def show
- expires_in 1.minute, public: true unless page_requested?
-
+ expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
end
DESCENDANTS_LIMIT = 60
+ before_action :require_signature!, if: :authorized_fetch_mode?
before_action :set_status
before_action :set_cache_headers
before_action :set_replies
def index
+ expires_in 0, public: public_fetch_mode?
render json: replies_collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', skip_activities: true
end
Rails.env.production?
end
+ def authorized_fetch_mode?
+ ENV['AUTHORIZED_FETCH'] == 'true'
+ end
+
+ def public_fetch_mode?
+ !authorized_fetch_mode?
+ end
+
def store_current_location
store_location_for(:user, request.url) unless request.format == :json
end
end
def set_cache_headers
- response.headers['Vary'] = 'Accept'
+ response.headers['Vary'] = 'Accept, Signature'
end
end
layout 'public'
before_action :set_instance_presenter
- before_action :set_link_headers
+ before_action :set_link_headers, if: -> { request.format.nil? || request.format == :html }
end
private
include DomainControlHelper
+ def require_signature!
+ render plain: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account
+ end
+
def signed_request?
request.headers['Signature'].present?
end
def signature_verification_failure_reason
- return @signature_verification_failure_reason if defined?(@signature_verification_failure_reason)
+ @signature_verification_failure_reason
+ end
+
+ def signature_verification_failure_code
+ @signature_verification_failure_code || 401
end
def signed_request_account
end
def account_from_key_id(key_id)
+ domain = key_id.start_with?('acct:') ? key_id.split('@').last : key_id
+
+ if domain_not_allowed?(domain)
+ @signature_verification_failure_code = 403
+ return
+ end
+
if key_id.start_with?('acct:')
stoplight_wrap_request { ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, '')) }
elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
- return if domain_not_allowed?(key_id)
-
account = ActivityPub::TagManager.instance.uri_to_resource(key_id, Account)
account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false) }
account
class FollowerAccountsController < ApplicationController
include AccountControllerConcern
+ include SignatureVerification
+ before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers
def index
end
format.json do
- raise Mastodon::NotPermittedError if params[:page].present? && @account.user_hides_network?
+ raise Mastodon::NotPermittedError if page_requested? && @account.user_hides_network?
- expires_in 3.minutes, public: true if params[:page].blank?
+ expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
render json: collection_presenter,
serializer: ActivityPub::CollectionSerializer,
@follows ||= Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
end
+ def page_requested?
+ params[:page].present?
+ end
+
def page_url(page)
account_followers_url(@account, page: page) unless page.nil?
end
def collection_presenter
- if params[:page].present?
+ if page_requested?
ActivityPub::CollectionPresenter.new(
id: account_followers_url(@account, page: params.fetch(:page, 1)),
type: :ordered,
class FollowingAccountsController < ApplicationController
include AccountControllerConcern
+ include SignatureVerification
+ before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers
def index
end
format.json do
- raise Mastodon::NotPermittedError if params[:page].present? && @account.user_hides_network?
+ raise Mastodon::NotPermittedError if page_requested? && @account.user_hides_network?
- expires_in 3.minutes, public: true if params[:page].blank?
+ expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
render json: collection_presenter,
serializer: ActivityPub::CollectionSerializer,
@follows ||= Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
end
+ def page_requested?
+ params[:page].present?
+ end
+
def page_url(page)
account_following_index_url(@account, page: page) unless page.nil?
end
def collection_presenter
- if params[:page].present?
+ if page_requested?
ActivityPub::CollectionPresenter.new(
id: account_following_index_url(@account, page: params.fetch(:page, 1)),
type: :ordered,
layout 'public'
+ before_action :require_signature!, only: :show, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_status
before_action :set_instance_presenter
before_action :set_link_headers
- before_action :redirect_to_original, only: [:show]
- before_action :set_referrer_policy_header, only: [:show]
+ before_action :redirect_to_original, only: :show
+ before_action :set_referrer_policy_header, only: :show
before_action :set_cache_headers
before_action :set_body_classes
before_action :set_autoplay, only: :embed
end
format.json do
- expires_in 3.minutes, public: @status.distributable?
+ expires_in 3.minutes, public: @status.distributable? && public_fetch_mode?
render json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter
end
end
end
def activity
- expires_in 3.minutes, public: @status.distributable?
+ expires_in 3.minutes, public: @status.distributable? && public_fetch_mode?
render json: @status, content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter
end
# frozen_string_literal: true
class TagsController < ApplicationController
+ include SignatureVerification
+
PAGE_SIZE = 20
layout 'public'
+ before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_tag
before_action :set_body_classes
before_action :set_instance_presenter
end
format.json do
- expires_in 3.minutes, public: true
+ expires_in 3.minutes, public: public_fetch_mode?
@statuses = HashtagQueryService.new.call(@tag, params.slice(:any, :all, :none), current_account, params[:local]).paginate_by_max_id(PAGE_SIZE, params[:max_id])
@statuses = cache_collection(@statuses, Status)
def serializable_hash(options = nil)
options = serialization_options(options)
serialized_hash = serializer.serializable_hash(options)
+ serialized_hash = serialized_hash.select { |k, _| options[:fields].include?(k) } if options[:fields]
serialized_hash = self.class.transform_key_casing!(serialized_hash, instance_options)
{ '@context' => serialized_context }.merge(serialized_hash)
RSpec.describe ActivityPub::InboxesController, type: :controller do
describe 'POST #create' do
- context 'if signed_request_account' do
+ context 'with signed_request_account' do
it 'returns 202' do
allow(controller).to receive(:signed_request_account) do
Fabricate(:account)
end
end
- context 'not signed_request_account' do
+ context 'without signed_request_account' do
it 'returns 401' do
allow(controller).to receive(:signed_request_account) do
false