Add collection endpoint (#37468)
This commit is contained in:
parent
2f91d9755d
commit
c218849204
@ -1,6 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::CollectionsController < ActivityPub::BaseController
|
||||
SUPPORTED_COLLECTIONS = %w(featured tags).freeze
|
||||
|
||||
vary_by -> { 'Signature' if authorized_fetch_mode? }
|
||||
|
||||
before_action :require_account_signature!, if: :authorized_fetch_mode?
|
||||
|
||||
46
app/controllers/collections_controller.rb
Normal file
46
app/controllers/collections_controller.rb
Normal file
@ -0,0 +1,46 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CollectionsController < ApplicationController
|
||||
include WebAppControllerConcern
|
||||
include SignatureAuthentication
|
||||
include Authorization
|
||||
include AccountOwnedConcern
|
||||
|
||||
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
|
||||
|
||||
before_action :check_feature_enabled
|
||||
before_action :require_account_signature!, only: :show, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_collection
|
||||
|
||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||
skip_before_action :require_functional!, only: :show, unless: :limited_federation_mode?
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
# TODO: format.html
|
||||
|
||||
format.json do
|
||||
expires_in expiration_duration, public: true if public_fetch_mode?
|
||||
render_with_cache json: @collection, content_type: 'application/activity+json', serializer: ActivityPub::FeaturedCollectionSerializer, adapter: ActivityPub::Adapter
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_collection
|
||||
@collection = @account.collections.find(params[:id])
|
||||
authorize @collection, :show?
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
def expiration_duration
|
||||
recently_updated = @collection.updated_at > 15.minutes.ago
|
||||
recently_updated ? 30.seconds : 5.minutes
|
||||
end
|
||||
|
||||
def check_feature_enabled
|
||||
raise ActionController::RoutingError unless Mastodon::Feature.collections_enabled?
|
||||
end
|
||||
end
|
||||
@ -63,7 +63,7 @@ class ActivityPub::TagManager
|
||||
when :flag
|
||||
target.uri
|
||||
when :featured_collection
|
||||
ap_account_featured_collection_url(target.account.id, target)
|
||||
ap_account_collection_url(target.account.id, target)
|
||||
end
|
||||
end
|
||||
|
||||
@ -135,7 +135,7 @@ class ActivityPub::TagManager
|
||||
def collection_uri_for(target, ...)
|
||||
raise ArgumentError, 'target must be a local account' unless target.local?
|
||||
|
||||
target.numeric_ap_id? ? ap_account_collection_url(target.id, ...) : account_collection_url(target, ...)
|
||||
target.numeric_ap_id? ? ap_account_actor_collection_url(target.id, ...) : account_actor_collection_url(target, ...)
|
||||
end
|
||||
|
||||
def inbox_uri_for(target)
|
||||
|
||||
@ -6,7 +6,7 @@ class CollectionPolicy < ApplicationPolicy
|
||||
end
|
||||
|
||||
def show?
|
||||
true
|
||||
current_account.nil? || (!owner_blocking? && !owner_blocking_domain?)
|
||||
end
|
||||
|
||||
def create?
|
||||
@ -24,6 +24,22 @@ class CollectionPolicy < ApplicationPolicy
|
||||
private
|
||||
|
||||
def owner?
|
||||
current_account == record.account
|
||||
current_account == owner
|
||||
end
|
||||
|
||||
def owner_blocking_domain?
|
||||
return false if current_account.nil? || current_account.domain.nil?
|
||||
|
||||
owner.domain_blocking?(current_account.domain)
|
||||
end
|
||||
|
||||
def owner_blocking?
|
||||
return false if current_account.nil?
|
||||
|
||||
current_account.blocked_by?(owner)
|
||||
end
|
||||
|
||||
def owner
|
||||
record.account
|
||||
end
|
||||
end
|
||||
|
||||
@ -96,14 +96,14 @@ Rails.application.routes.draw do
|
||||
get '/authorize_follow', to: redirect { |_, request| "/authorize_interaction?#{request.params.to_query}" }
|
||||
|
||||
concern :account_resources do
|
||||
resources :featured_collections, only: [:show]
|
||||
resources :collections, only: [:show], constraints: { id: /\d+/ }
|
||||
resources :followers, only: [:index], controller: :follower_accounts
|
||||
resources :following, only: [:index], controller: :following_accounts
|
||||
|
||||
scope module: :activitypub do
|
||||
resource :outbox, only: [:show]
|
||||
resource :inbox, only: [:create]
|
||||
resources :collections, only: [:show]
|
||||
resources :collections, only: [:show], as: :actor_collections, constraints: { id: Regexp.union(ActivityPub::CollectionsController::SUPPORTED_COLLECTIONS) }
|
||||
resource :followers_synchronization, only: [:show]
|
||||
resources :quote_authorizations, only: [:show]
|
||||
end
|
||||
|
||||
@ -198,7 +198,7 @@ RSpec.describe ActivityPub::TagManager do
|
||||
|
||||
it 'returns a string starting with web domain and with the expected path' do
|
||||
expect(subject.uri_for(collection))
|
||||
.to eq("#{host_prefix}/ap/users/#{collection.account.id}/featured_collections/#{collection.id}")
|
||||
.to eq("#{host_prefix}/ap/users/#{collection.account.id}/collections/#{collection.id}")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -16,11 +16,23 @@ RSpec.describe CollectionPolicy do
|
||||
end
|
||||
|
||||
permissions :show? do
|
||||
it 'permits everyone to show' do
|
||||
it 'permits when no user is given' do
|
||||
expect(policy).to permit(nil, collection)
|
||||
end
|
||||
|
||||
it 'permits unblocked users' do
|
||||
expect(policy).to permit(owner, collection)
|
||||
expect(policy).to permit(other_user, collection)
|
||||
end
|
||||
|
||||
it 'denies blocked users' do
|
||||
domain_blocked_user = Fabricate(:remote_account)
|
||||
owner.block_domain!(domain_blocked_user.domain)
|
||||
owner.block!(other_user)
|
||||
|
||||
expect(policy).to_not permit(domain_blocked_user, collection)
|
||||
expect(policy).to_not permit(other_user, collection)
|
||||
end
|
||||
end
|
||||
|
||||
permissions :create? do
|
||||
|
||||
@ -14,7 +14,7 @@ RSpec.describe 'ActivityPub Collections' do
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
subject { get account_collection_path(id: id, account_username: account.username), headers: nil, sign_with: remote_account }
|
||||
subject { get account_actor_collection_path(id: id, account_username: account.username), headers: nil, sign_with: remote_account }
|
||||
|
||||
context 'when id is "featured"' do
|
||||
let(:id) { 'featured' }
|
||||
@ -131,16 +131,5 @@ RSpec.describe 'ActivityPub Collections' do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when id is not "featured"' do
|
||||
let(:id) { 'hoge' }
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response)
|
||||
.to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
138
spec/requests/collections_spec.rb
Normal file
138
spec/requests/collections_spec.rb
Normal file
@ -0,0 +1,138 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Collections' do
|
||||
describe 'GET /@:account_username/collections/:id', feature: :collections do
|
||||
subject { get account_collection_path(account, collection, format: :json) }
|
||||
|
||||
let(:collection) { Fabricate(:collection) }
|
||||
let(:account) { collection.account }
|
||||
|
||||
context 'when signed out' do
|
||||
context 'when account is permanently suspended' do
|
||||
before do
|
||||
account.suspend!
|
||||
account.deletion_request.destroy
|
||||
end
|
||||
|
||||
it 'returns http gone' do
|
||||
subject
|
||||
|
||||
expect(response)
|
||||
.to have_http_status(410)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when account is temporarily suspended' do
|
||||
before { account.suspend! }
|
||||
|
||||
it 'returns http forbidden' do
|
||||
subject
|
||||
|
||||
expect(response)
|
||||
.to have_http_status(403)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when account is accessible' do
|
||||
context 'with JSON' do
|
||||
subject { get ap_account_collection_path(account.id, collection, format: :json) }
|
||||
|
||||
it 'renders ActivityPub FeaturedCollection object successfully', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response)
|
||||
.to have_http_status(200)
|
||||
.and have_cacheable_headers.with_vary('Accept, Accept-Language, Cookie')
|
||||
|
||||
expect(response.headers).to include(
|
||||
'Content-Type' => include('application/activity+json')
|
||||
)
|
||||
expect(response.parsed_body)
|
||||
.to include({
|
||||
'type' => 'FeaturedCollection',
|
||||
'name' => collection.name,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when signed in' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
before do
|
||||
post user_session_path, params: { user: { email: user.email, password: user.password } }
|
||||
end
|
||||
|
||||
context 'when account blocks user' do
|
||||
before { account.block!(user.account) }
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response)
|
||||
.to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with "HTTP Signature" access signed by a remote account' do
|
||||
subject do
|
||||
get account_collection_path(account, collection, format: :json),
|
||||
headers: nil,
|
||||
sign_with: remote_account
|
||||
end
|
||||
|
||||
let(:remote_account) { Fabricate(:account, domain: 'host.example') }
|
||||
|
||||
context 'when account blocks the remote account' do
|
||||
before { account.block!(remote_account) }
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response)
|
||||
.to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when account domain blocks the domain of the remote account' do
|
||||
before { account.block_domain!(remote_account.domain) }
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response)
|
||||
.to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with JSON' do
|
||||
subject do
|
||||
get ap_account_collection_path(account.id, collection, format: :json),
|
||||
headers: nil,
|
||||
sign_with: remote_account
|
||||
end
|
||||
|
||||
it 'renders ActivityPub FeaturedCollection object successfully', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response)
|
||||
.to have_http_status(200)
|
||||
.and have_cacheable_headers.with_vary('Accept, Accept-Language, Cookie')
|
||||
|
||||
expect(response.headers).to include(
|
||||
'Content-Type' => include('application/activity+json')
|
||||
)
|
||||
expect(response.parsed_body)
|
||||
.to include({
|
||||
'type' => 'FeaturedCollection',
|
||||
'name' => collection.name,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
x
Reference in New Issue
Block a user