Merge remote-tracking branch 'upstream/main'
All checks were successful
continuous-integration/drone Build is passing

This commit is contained in:
Dalite 2023-12-01 10:57:58 +01:00
commit 3dd4a94c57
71 changed files with 415 additions and 309 deletions

View file

@ -26,7 +26,7 @@ Lint/NonLocalExitFromIterator:
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize:
Max: 144
Max: 125
# Configuration parameters: CountBlocks, Max.
Metrics/BlockNesting:

View file

@ -247,7 +247,9 @@ RUN \
RUN \
# Pre-create and chown system volume to Mastodon user
mkdir -p /opt/mastodon/public/system; \
chown mastodon:mastodon /opt/mastodon/public/system;
chown mastodon:mastodon /opt/mastodon/public/system; \
# Set Mastodon user as owner of tmp folder
chown -R mastodon:mastodon /opt/mastodon/tmp;
# Set the running user for resulting container
USER mastodon

View file

@ -50,7 +50,7 @@ class AccountsController < ApplicationController
end
def only_media_scope
Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id)
Status.joins(:media_attachments).merge(@account.media_attachments).group(:id)
end
def no_replies_scope

View file

@ -4,7 +4,7 @@ require 'csv'
module Admin
class ExportDomainAllowsController < BaseController
include AdminExportControllerConcern
include Admin::ExportControllerConcern
before_action :set_dummy_import!, only: [:new]

View file

@ -4,7 +4,7 @@ require 'csv'
module Admin
class ExportDomainBlocksController < BaseController
include AdminExportControllerConcern
include Admin::ExportControllerConcern
before_action :set_dummy_import!, only: [:new]

View file

@ -4,9 +4,9 @@ class Api::BaseController < ApplicationController
DEFAULT_STATUSES_LIMIT = 20
DEFAULT_ACCOUNTS_LIMIT = 40
include RateLimitHeaders
include AccessTokenTrackingConcern
include ApiCachingConcern
include Api::RateLimitHeaders
include Api::AccessTokenTrackingConcern
include Api::CachingConcern
include Api::ContentSecurityPolicy
skip_before_action :require_functional!, unless: :limited_federation_mode?
@ -105,7 +105,7 @@ class Api::BaseController < ApplicationController
end
def require_not_suspended!
render json: { error: 'Your login is currently disabled' }, status: 403 if current_user&.account&.suspended?
render json: { error: 'Your login is currently disabled' }, status: 403 if current_user&.account&.unavailable?
end
def require_user!

View file

@ -26,7 +26,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
end
def hide_results?
@account.suspended? || (@account.hides_followers? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
@account.unavailable? || (@account.hides_followers? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
end
def default_accounts

View file

@ -26,7 +26,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
end
def hide_results?
@account.suspended? || (@account.hides_following? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
@account.unavailable? || (@account.hides_following? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
end
def default_accounts

View file

@ -19,7 +19,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
end
def load_statuses
@account.suspended? ? [] : cached_account_statuses
@account.unavailable? ? [] : cached_account_statuses
end
def cached_account_statuses

View file

@ -8,6 +8,11 @@ class Api::V2::SearchController < Api::BaseController
before_action -> { authorize_if_got_token! :read, :'read:search' }
before_action :validate_search_params!
with_options unless: :user_signed_in? do
before_action :query_pagination_error, if: :pagination_requested?
before_action :remote_resolve_error, if: :remote_resolve_requested?
end
def index
@search = Search.new(search_results)
render json: @search, serializer: REST::SearchSerializer
@ -21,12 +26,22 @@ class Api::V2::SearchController < Api::BaseController
def validate_search_params!
params.require(:q)
end
return if user_signed_in?
def query_pagination_error
render json: { error: 'Search queries pagination is not supported without authentication' }, status: 401
end
return render json: { error: 'Search queries pagination is not supported without authentication' }, status: 401 if params[:offset].present?
def remote_resolve_error
render json: { error: 'Search queries that resolve remote resources are not supported without authentication' }, status: 401
end
render json: { error: 'Search queries that resolve remote resources are not supported without authentication' }, status: 401 if truthy_param?(:resolve)
def remote_resolve_requested?
truthy_param?(:resolve)
end
def pagination_requested?
params[:offset].present?
end
def search_results
@ -34,7 +49,15 @@ class Api::V2::SearchController < Api::BaseController
params[:q],
current_account,
limit_param(RESULTS_LIMIT),
search_params.merge(resolve: truthy_param?(:resolve), exclude_unreviewed: truthy_param?(:exclude_unreviewed), following: truthy_param?(:following))
combined_search_params
)
end
def combined_search_params
search_params.merge(
resolve: truthy_param?(:resolve),
exclude_unreviewed: truthy_param?(:exclude_unreviewed),
following: truthy_param?(:following)
)
end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class Auth::ConfirmationsController < Devise::ConfirmationsController
include CaptchaConcern
include Auth::CaptchaConcern
layout 'auth'

View file

@ -2,7 +2,7 @@
class Auth::RegistrationsController < Devise::RegistrationsController
include RegistrationHelper
include RegistrationSpamConcern
include Auth::RegistrationSpamConcern
layout :determine_layout
@ -120,7 +120,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end
def require_not_suspended!
forbidden if current_account.suspended?
forbidden if current_account.unavailable?
end
def set_rules

View file

@ -10,7 +10,7 @@ class Auth::SessionsController < Devise::SessionsController
prepend_before_action :check_suspicious!, only: [:create]
include TwoFactorAuthenticationConcern
include Auth::TwoFactorAuthenticationConcern
before_action :set_body_classes

View file

@ -34,8 +34,8 @@ module AccountOwnedConcern
end
def check_account_suspension
if @account.suspended_permanently?
permanent_suspension_response
if @account.permanently_unavailable?
permanent_unavailability_response
elsif @account.suspended? && !skip_temporary_suspension_response?
temporary_suspension_response
end
@ -45,7 +45,7 @@ module AccountOwnedConcern
false
end
def permanent_suspension_response
def permanent_unavailability_response
expires_in(3.minutes, public: true)
gone
end

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true
module AdminExportControllerConcern
module Admin::ExportControllerConcern
extend ActiveSupport::Concern
private

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true
module AccessTokenTrackingConcern
module Api::AccessTokenTrackingConcern
extend ActiveSupport::Concern
ACCESS_TOKEN_UPDATE_FREQUENCY = 24.hours.freeze

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true
module ApiCachingConcern
module Api::CachingConcern
extend ActiveSupport::Concern
def cache_if_unauthenticated!

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true
module RateLimitHeaders
module Api::RateLimitHeaders
extend ActiveSupport::Concern
class_methods do

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true
module CaptchaConcern
module Auth::CaptchaConcern
extend ActiveSupport::Concern
include Hcaptcha::Adapters::ViewMethods

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true
module RegistrationSpamConcern
module Auth::RegistrationSpamConcern
extend ActiveSupport::Concern
def set_registration_form_time

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true
module TwoFactorAuthenticationConcern
module Auth::TwoFactorAuthenticationConcern
extend ActiveSupport::Concern
included do

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true
module ExportControllerConcern
module Settings::ExportControllerConcern
extend ActiveSupport::Concern
included do

View file

@ -31,7 +31,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
end
def require_not_suspended!
forbidden if current_account.suspended?
forbidden if current_account.unavailable?
end
def set_cache_headers

View file

@ -18,6 +18,6 @@ class Settings::BaseController < ApplicationController
end
def require_not_suspended!
forbidden if current_account.suspended?
forbidden if current_account.unavailable?
end
end

View file

@ -25,7 +25,7 @@ class Settings::DeletesController < Settings::BaseController
end
def require_not_suspended!
forbidden if current_account.suspended?
forbidden if current_account.unavailable?
end
def challenge_passed?

View file

@ -3,7 +3,7 @@
module Settings
module Exports
class BlockedAccountsController < BaseController
include ExportControllerConcern
include Settings::ExportControllerConcern
def index
send_export_file

View file

@ -3,7 +3,7 @@
module Settings
module Exports
class BlockedDomainsController < BaseController
include ExportControllerConcern
include Settings::ExportControllerConcern
def index
send_export_file

View file

@ -3,7 +3,7 @@
module Settings
module Exports
class BookmarksController < BaseController
include ExportControllerConcern
include Settings::ExportControllerConcern
def index
send_export_file

View file

@ -3,7 +3,7 @@
module Settings
module Exports
class FollowingAccountsController < BaseController
include ExportControllerConcern
include Settings::ExportControllerConcern
def index
send_export_file

View file

@ -3,7 +3,7 @@
module Settings
module Exports
class ListsController < BaseController
include ExportControllerConcern
include Settings::ExportControllerConcern
def index
send_export_file

View file

@ -3,7 +3,7 @@
module Settings
module Exports
class MutedAccountsController < BaseController
include ExportControllerConcern
include Settings::ExportControllerConcern
def index
send_export_file

View file

@ -42,7 +42,7 @@ module WellKnown
end
def check_account_suspension
gone if @account.suspended_permanently?
gone if @account.permanently_unavailable?
end
def gone

View file

@ -1,11 +1,18 @@
/* eslint-disable @typescript-eslint/no-unsafe-call,
@typescript-eslint/no-unsafe-return,
@typescript-eslint/no-unsafe-assignment,
@typescript-eslint/no-unsafe-member-access
-- the settings store is not yet typed */
import type { PropsWithChildren } from 'react';
import { useCallback, useState } from 'react';
import { useCallback, useState, useEffect } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import { changeSetting } from 'mastodon/actions/settings';
import { bannerSettings } from 'mastodon/settings';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
import { IconButton } from './icon_button';
@ -21,13 +28,25 @@ export const DismissableBanner: React.FC<PropsWithChildren<Props>> = ({
id,
children,
}) => {
const [visible, setVisible] = useState(!bannerSettings.get(id));
const dismissed = useAppSelector((state) =>
state.settings.getIn(['dismissed_banners', id], false),
);
const dispatch = useAppDispatch();
const [visible, setVisible] = useState(!bannerSettings.get(id) && !dismissed);
const intl = useIntl();
const handleDismiss = useCallback(() => {
setVisible(false);
bannerSettings.set(id, true);
}, [id]);
dispatch(changeSetting(['dismissed_banners', id], true));
}, [id, dispatch]);
useEffect(() => {
if (!visible && !dismissed) {
dispatch(changeSetting(['dismissed_banners', id], true));
}
}, [id, dispatch, visible, dismissed]);
if (!visible) {
return null;

View file

@ -100,6 +100,15 @@ const initialState = ImmutableMap({
body: '',
}),
}),
dismissed_banners: ImmutableMap({
'public_timeline': false,
'community_timeline': false,
'home.explore_prompt': false,
'explore/links': false,
'explore/statuses': false,
'explore/tags': false,
}),
});
const defaultColumns = fromJS([

View file

@ -2732,22 +2732,16 @@ $ui-header-height: 55px;
&__description {
flex: 1 1 auto;
line-height: 20px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
h6 {
color: $highlight-text-color;
font-weight: 500;
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
}
p {
color: $darker-text-color;
overflow: hidden;
text-overflow: ellipsis;
}
}
}

View file

@ -32,7 +32,7 @@ class AccountStatusesFilter
private
def initial_scope
return Status.none if suspended?
return Status.none if account.unavailable?
if anonymous?
account.statuses.where(visibility: %i(public unlisted))
@ -70,7 +70,7 @@ class AccountStatusesFilter
end
def only_media_scope
Status.joins(:media_attachments).merge(account.media_attachments.reorder(nil)).group(Status.arel_table[:id])
Status.joins(:media_attachments).merge(account.media_attachments).group(Status.arel_table[:id])
end
def no_replies_scope
@ -95,10 +95,6 @@ class AccountStatusesFilter
end
end
def suspended?
account.suspended?
end
def anonymous?
current_account.nil?
end

View file

@ -9,7 +9,7 @@ class ActivityPub::Activity::Move < ActivityPub::Activity
target_account = ActivityPub::FetchRemoteAccountService.new.call(target_uri)
if target_account.nil? || target_account.suspended? || !target_account.also_known_as.include?(origin_account.uri)
if target_account.nil? || target_account.unavailable? || !target_account.also_known_as.include?(origin_account.uri)
unmark_as_processing!
return
end

View file

@ -9,8 +9,8 @@ class ContentSecurityPolicy
url_from_configured_asset_host || url_from_base_host
end
def media_host
cdn_host_value || assets_host
def media_hosts
[assets_host, cdn_host_value].compact
end
private

View file

@ -27,11 +27,11 @@ class Vacuum::MediaAttachmentsVacuum
end
def media_attachments_past_retention_period
MediaAttachment.unscoped.remote.cached.where(MediaAttachment.arel_table[:created_at].lt(@retention_period.ago)).where(MediaAttachment.arel_table[:updated_at].lt(@retention_period.ago))
MediaAttachment.remote.cached.where(MediaAttachment.arel_table[:created_at].lt(@retention_period.ago)).where(MediaAttachment.arel_table[:updated_at].lt(@retention_period.ago))
end
def orphaned_media_attachments
MediaAttachment.unscoped.unattached.where(MediaAttachment.arel_table[:created_at].lt(TTL.ago))
MediaAttachment.unattached.where(MediaAttachment.arel_table[:created_at].lt(TTL.ago))
end
def retention_period?

View file

@ -246,6 +246,9 @@ class Account < ApplicationRecord
suspended? && deletion_request.present?
end
alias unavailable? suspended?
alias permanently_unavailable? suspended_permanently?
def suspend!(date: Time.now.utc, origin: :local, block_email: true)
transaction do
create_deletion_request!

View file

@ -32,7 +32,7 @@ class Admin::StatusFilter
def scope_for(key, _value)
case key.to_s
when 'media'
Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id).reorder('statuses.id desc')
Status.joins(:media_attachments).merge(@account.media_attachments).group(:id).reorder('statuses.id desc')
else
raise Mastodon::InvalidParameterError, "Unknown filter: #{key}"
end

View file

@ -205,12 +205,11 @@ class MediaAttachment < ApplicationRecord
validates :thumbnail, absence: true, if: -> { local? && !audio_or_video? }
scope :attached, -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) }
scope :unattached, -> { where(status_id: nil, scheduled_status_id: nil) }
scope :local, -> { where(remote_url: '') }
scope :remote, -> { where.not(remote_url: '') }
scope :cached, -> { remote.where.not(file_file_name: nil) }
default_scope { order(id: :asc) }
scope :local, -> { where(remote_url: '') }
scope :ordered, -> { order(id: :asc) }
scope :remote, -> { where.not(remote_url: '') }
scope :unattached, -> { where(status_id: nil, scheduled_status_id: nil) }
attr_accessor :skip_download

View file

@ -250,7 +250,7 @@ class User < ApplicationRecord
end
def functional_or_moved?
confirmed? && approved? && !disabled? && !account.suspended? && !account.memorial?
confirmed? && approved? && !disabled? && !account.unavailable? && !account.memorial?
end
def unconfirmed?

View file

@ -8,7 +8,7 @@ class StatusPolicy < ApplicationPolicy
end
def show?
return false if author.suspended?
return false if author.unavailable?
if requires_mention?
owned? || mention_exists?

View file

@ -96,19 +96,19 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
end
def discoverable
object.suspended? ? false : (object.discoverable || false)
object.unavailable? ? false : (object.discoverable || false)
end
def indexable
object.suspended? ? false : (object.indexable || false)
object.unavailable? ? false : (object.indexable || false)
end
def name
object.suspended? ? object.username : (object.display_name.presence || object.username)
object.unavailable? ? object.username : (object.display_name.presence || object.username)
end
def summary
object.suspended? ? '' : account_bio_format(object)
object.unavailable? ? '' : account_bio_format(object)
end
def icon
@ -132,23 +132,23 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
end
def avatar_exists?
!object.suspended? && object.avatar?
!object.unavailable? && object.avatar?
end
def header_exists?
!object.suspended? && object.header?
!object.unavailable? && object.header?
end
def manually_approves_followers
object.suspended? ? false : object.locked
object.unavailable? ? false : object.locked
end
def virtual_tags
object.suspended? ? [] : (object.emojis + object.tags)
object.unavailable? ? [] : (object.emojis + object.tags)
end
def virtual_attachments
object.suspended? ? [] : object.fields
object.unavailable? ? [] : object.fields
end
def moved_to
@ -156,11 +156,11 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
end
def moved?
!object.suspended? && object.moved?
!object.unavailable? && object.moved?
end
def also_known_as?
!object.suspended? && !object.also_known_as.empty?
!object.unavailable? && !object.also_known_as.empty?
end
def published

View file

@ -39,18 +39,18 @@ class InitialStateSerializer < ActiveModel::Serializer
if object.current_account
store[:me] = object.current_account.id.to_s
store[:unfollow_modal] = object.current_account.user.setting_unfollow_modal
store[:boost_modal] = object.current_account.user.setting_boost_modal
store[:delete_modal] = object.current_account.user.setting_delete_modal
store[:auto_play_gif] = object.current_account.user.setting_auto_play_gif
store[:display_media] = object.current_account.user.setting_display_media
store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers
store[:reduce_motion] = object.current_account.user.setting_reduce_motion
store[:disable_swiping] = object.current_account.user.setting_disable_swiping
store[:advanced_layout] = object.current_account.user.setting_advanced_layout
store[:use_blurhash] = object.current_account.user.setting_use_blurhash
store[:use_pending_items] = object.current_account.user.setting_use_pending_items
store[:show_trends] = Setting.trends && object.current_account.user.setting_trends
store[:unfollow_modal] = object_account_user.setting_unfollow_modal
store[:boost_modal] = object_account_user.setting_boost_modal
store[:delete_modal] = object_account_user.setting_delete_modal
store[:auto_play_gif] = object_account_user.setting_auto_play_gif
store[:display_media] = object_account_user.setting_display_media
store[:expand_spoilers] = object_account_user.setting_expand_spoilers
store[:reduce_motion] = object_account_user.setting_reduce_motion
store[:disable_swiping] = object_account_user.setting_disable_swiping
store[:advanced_layout] = object_account_user.setting_advanced_layout
store[:use_blurhash] = object_account_user.setting_use_blurhash
store[:use_pending_items] = object_account_user.setting_use_pending_items
store[:show_trends] = Setting.trends && object_account_user.setting_trends
else
store[:auto_play_gif] = Setting.auto_play_gif
store[:display_media] = Setting.display_media
@ -71,9 +71,9 @@ class InitialStateSerializer < ActiveModel::Serializer
if object.current_account
store[:me] = object.current_account.id.to_s
store[:default_privacy] = object.visibility || object.current_account.user.setting_default_privacy
store[:default_sensitive] = object.current_account.user.setting_default_sensitive
store[:default_language] = object.current_account.user.preferred_posting_language
store[:default_privacy] = object.visibility || object_account_user.setting_default_privacy
store[:default_sensitive] = object_account_user.setting_default_sensitive
store[:default_language] = object_account_user.preferred_posting_language
end
store[:text] = object.text if object.text
@ -89,11 +89,11 @@ class InitialStateSerializer < ActiveModel::Serializer
associations: [:account_stat, :user, { moved_to_account: [:account_stat, :user] }]
)
store[object.current_account.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.current_account, serializer: REST::AccountSerializer) if object.current_account
store[object.admin.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.admin, serializer: REST::AccountSerializer) if object.admin
store[object.owner.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.owner, serializer: REST::AccountSerializer) if object.owner
store[object.disabled_account.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.disabled_account, serializer: REST::AccountSerializer) if object.disabled_account
store[object.moved_to_account.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.moved_to_account, serializer: REST::AccountSerializer) if object.moved_to_account
store[object.current_account.id.to_s] = serialized_account(object.current_account) if object.current_account
store[object.admin.id.to_s] = serialized_account(object.admin) if object.admin
store[object.owner.id.to_s] = serialized_account(object.owner) if object.owner
store[object.disabled_account.id.to_s] = serialized_account(object.disabled_account) if object.disabled_account
store[object.moved_to_account.id.to_s] = serialized_account(object.moved_to_account) if object.moved_to_account
store
end
@ -108,6 +108,14 @@ class InitialStateSerializer < ActiveModel::Serializer
private
def object_account_user
object.current_account.user
end
def serialized_account(account)
ActiveModelSerializers::SerializableResource.new(account, serializer: REST::AccountSerializer)
end
def instance_presenter
@instance_presenter ||= InstancePresenter.new
end

View file

@ -61,7 +61,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
end
def note
object.suspended? ? '' : account_bio_format(object)
object.unavailable? ? '' : account_bio_format(object)
end
def url
@ -73,19 +73,19 @@ class REST::AccountSerializer < ActiveModel::Serializer
end
def avatar
full_asset_url(object.suspended? ? object.avatar.default_url : object.avatar_original_url)
full_asset_url(object.unavailable? ? object.avatar.default_url : object.avatar_original_url)
end
def avatar_static
full_asset_url(object.suspended? ? object.avatar.default_url : object.avatar_static_url)
full_asset_url(object.unavailable? ? object.avatar.default_url : object.avatar_static_url)
end
def header
full_asset_url(object.suspended? ? object.header.default_url : object.header_original_url)
full_asset_url(object.unavailable? ? object.header.default_url : object.header_original_url)
end
def header_static
full_asset_url(object.suspended? ? object.header.default_url : object.header_static_url)
full_asset_url(object.unavailable? ? object.header.default_url : object.header_static_url)
end
def created_at
@ -97,39 +97,39 @@ class REST::AccountSerializer < ActiveModel::Serializer
end
def display_name
object.suspended? ? '' : object.display_name
object.unavailable? ? '' : object.display_name
end
def locked
object.suspended? ? false : object.locked
object.unavailable? ? false : object.locked
end
def bot
object.suspended? ? false : object.bot
object.unavailable? ? false : object.bot
end
def discoverable
object.suspended? ? false : object.discoverable
object.unavailable? ? false : object.discoverable
end
def indexable
object.suspended? ? false : object.indexable
object.unavailable? ? false : object.indexable
end
def moved_to_account
object.suspended? ? nil : AccountDecorator.new(object.moved_to_account)
object.unavailable? ? nil : AccountDecorator.new(object.moved_to_account)
end
def emojis
object.suspended? ? [] : object.emojis
object.unavailable? ? [] : object.emojis
end
def fields
object.suspended? ? [] : object.fields
object.unavailable? ? [] : object.fields
end
def suspended
object.suspended?
object.unavailable?
end
def silenced
@ -141,7 +141,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
end
def roles
if object.suspended? || object.user.nil?
if object.unavailable? || object.user.nil?
[]
else
[object.user.role].compact.filter(&:highlighted?)

View file

@ -72,7 +72,7 @@ class BackupService < BaseService
end
def dump_media_attachments!(zipfile)
MediaAttachment.attached.where(account: account).reorder(nil).find_in_batches do |media_attachments|
MediaAttachment.attached.where(account: account).find_in_batches do |media_attachments|
media_attachments.each do |m|
path = m.file&.path
next unless path

View file

@ -43,7 +43,7 @@ class ClearDomainMediaService < BaseService
end
def media_from_blocked_domain
MediaAttachment.joins(:account).merge(blocked_domain_accounts).reorder(nil)
MediaAttachment.joins(:account).merge(blocked_domain_accounts)
end
def emojis_from_blocked_domains

View file

@ -165,7 +165,7 @@ class DeleteAccountService < BaseService
end
def purge_media_attachments!
@account.media_attachments.reorder(nil).find_each do |media_attachment|
@account.media_attachments.find_each do |media_attachment|
next if keep_account_record? && reported_status_ids.include?(media_attachment.status_id)
media_attachment.destroy

View file

@ -50,7 +50,7 @@ class FollowService < BaseService
end
def following_not_possible?
@target_account.nil? || @target_account.id == @source_account.id || @target_account.suspended?
@target_account.nil? || @target_account.id == @source_account.id || @target_account.unavailable?
end
def following_not_allowed?

View file

@ -108,7 +108,7 @@ class NotifyService < BaseService
end
def blocked?
blocked = @recipient.suspended?
blocked = @recipient.unavailable?
blocked ||= from_self? && @notification.type != :poll
return blocked if message? && from_staff?

View file

@ -51,7 +51,7 @@ class ProcessMentionsService < BaseService
# If after resolving it still isn't found or isn't the right
# protocol, then give up
next match if mention_undeliverable?(mentioned_account) || mentioned_account&.suspended?
next match if mention_undeliverable?(mentioned_account) || mentioned_account&.unavailable?
mention = @previous_mentions.find { |x| x.account_id == mentioned_account.id }
mention ||= @current_mentions.find { |x| x.account_id == mentioned_account.id }

View file

@ -12,7 +12,7 @@ class ReportService < BaseService
@rule_ids = options.delete(:rule_ids).presence
@options = options
raise ActiveRecord::RecordNotFound if @target_account.suspended?
raise ActiveRecord::RecordNotFound if @target_account.unavailable?
create_report!
notify_staff!

View file

@ -65,7 +65,7 @@ class SuspendAccountService < BaseService
def privatize_media_attachments!
attachment_names = MediaAttachment.attachment_definitions.keys
@account.media_attachments.reorder(nil).find_each do |media_attachment|
@account.media_attachments.find_each do |media_attachment|
attachment_names.each do |attachment_name|
attachment = media_attachment.public_send(attachment_name)
styles = MediaAttachment::DEFAULT_STYLES | attachment.styles.keys

View file

@ -61,7 +61,7 @@ class UnsuspendAccountService < BaseService
def publish_media_attachments!
attachment_names = MediaAttachment.attachment_definitions.keys
@account.media_attachments.reorder(nil).find_each do |media_attachment|
@account.media_attachments.find_each do |media_attachment|
attachment_names.each do |attachment_name|
attachment = media_attachment.public_send(attachment_name)
styles = MediaAttachment::DEFAULT_STYLES | attachment.styles.keys

View file

@ -1,4 +1,4 @@
.batch-table__row{ class: [!account.suspended? && account.user_pending? && 'batch-table__row--attention', (account.suspended? || account.user_unconfirmed?) && 'batch-table__row--muted'] }
.batch-table__row{ class: [!account.unavailable? && account.user_pending? && 'batch-table__row--attention', (account.unavailable? || account.user_unconfirmed?) && 'batch-table__row--muted'] }
%label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
= f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id
.batch-table__row__content.batch-table__row__content--unpadded
@ -8,13 +8,13 @@
%td
= account_link_to account, path: admin_account_path(account.id)
%td.accounts-table__count.optional
- if account.suspended? || account.user_pending?
- if account.unavailable? || account.user_pending?
\-
- else
= friendly_number_to_human account.statuses_count
%small= t('accounts.posts', count: account.statuses_count).downcase
%td.accounts-table__count.optional
- if account.suspended? || account.user_pending?
- if account.unavailable? || account.user_pending?
\-
- else
= friendly_number_to_human account.followers_count
@ -30,6 +30,6 @@
\-
%br/
%samp.ellipsized-ip= relevant_account_ip(account, params[:ip])
- if !account.suspended? && account.user_pending? && account.user&.invite_request&.text.present?
- if !account.unavailable? && account.user_pending? && account.user&.invite_request&.text.present?
.batch-table__row__content__quote
%p= account.user&.invite_request&.text

View file

@ -27,7 +27,7 @@
= t('doorkeeper.authorized_applications.index.authorized_at', date: l(application.created_at.to_date))
- unless application.superapp? || current_account.suspended?
- unless application.superapp? || current_account.unavailable?
%div
= table_link_to 'times', t('doorkeeper.authorized_applications.buttons.revoke'), oauth_authorized_application_path(application), method: :delete, data: { confirm: t('doorkeeper.authorized_applications.confirmations.revoke') }

View file

@ -7,7 +7,7 @@ class AccountDeletionWorker
def perform(account_id, options = {})
account = Account.find(account_id)
return unless account.suspended?
return unless account.unavailable?
reserve_username = options.with_indifferent_access.fetch(:reserve_username, true)
skip_activitypub = options.with_indifferent_access.fetch(:skip_activitypub, false)

View file

@ -21,12 +21,12 @@ class Scheduler::SuspendedUserCleanupScheduler
def perform
return if Sidekiq::Queue.new('pull').size > MAX_PULL_SIZE
clean_suspended_accounts!
process_deletion_requests!
end
private
def clean_suspended_accounts!
def process_deletion_requests!
# This should be fine because we only process a small amount of deletion requests at once and
# `id` and `created_at` should follow the same order.
AccountDeletionRequest.reorder(id: :asc).take(MAX_DELETIONS_PER_JOB).each do |deletion_request|

View file

@ -10,7 +10,7 @@ require_relative '../../app/lib/content_security_policy'
policy = ContentSecurityPolicy.new
assets_host = policy.assets_host
media_host = policy.media_host
media_hosts = policy.media_hosts
def sso_host
return unless ENV['ONE_CLICK_SSO_LOGIN'] == 'true'
@ -35,9 +35,9 @@ Rails.application.config.content_security_policy do |p|
p.default_src :none
p.frame_ancestors :none
p.font_src :self, assets_host
p.img_src :self, :https, :data, :blob, assets_host
p.img_src :self, :data, :blob, *media_hosts
p.style_src :self, assets_host
p.media_src :self, :https, :data, assets_host
p.media_src :self, :data, *media_hosts
p.frame_src :self, :https
p.manifest_src :self, assets_host
@ -54,10 +54,10 @@ Rails.application.config.content_security_policy do |p|
webpacker_public_host = ENV.fetch('WEBPACKER_DEV_SERVER_PUBLIC', Webpacker.config.dev_server[:public])
webpacker_urls = %w(ws http).map { |protocol| "#{protocol}#{Webpacker.dev_server.https? ? 's' : ''}://#{webpacker_public_host}" }
p.connect_src :self, :data, :blob, assets_host, media_host, Rails.configuration.x.streaming_api_base_url, *webpacker_urls
p.connect_src :self, :data, :blob, *media_hosts, Rails.configuration.x.streaming_api_base_url, *webpacker_urls
p.script_src :self, :unsafe_inline, :unsafe_eval, assets_host
else
p.connect_src :self, :data, :blob, assets_host, media_host, Rails.configuration.x.streaming_api_base_url
p.connect_src :self, :data, :blob, *media_hosts, Rails.configuration.x.streaming_api_base_url
p.script_src :self, assets_host, "'wasm-unsafe-eval'"
end
end

View file

@ -13,21 +13,22 @@
# end
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym 'StatsD'
inflect.acronym 'OEmbed'
inflect.acronym 'OStatus'
inflect.acronym 'ActivityPub'
inflect.acronym 'PubSubHubbub'
inflect.acronym 'ActivityStreams'
inflect.acronym 'JsonLd'
inflect.acronym 'Ed25519'
inflect.acronym 'TOC'
inflect.acronym 'RSS'
inflect.acronym 'REST'
inflect.acronym 'URL'
inflect.acronym 'ASCII'
inflect.acronym 'CLI'
inflect.acronym 'DeepL'
inflect.acronym 'DSL'
inflect.acronym 'Ed25519'
inflect.acronym 'JsonLd'
inflect.acronym 'OEmbed'
inflect.acronym 'OStatus'
inflect.acronym 'PubSubHubbub'
inflect.acronym 'REST'
inflect.acronym 'RSS'
inflect.acronym 'StatsD'
inflect.acronym 'TOC'
inflect.acronym 'URL'
inflect.singular 'data', 'data'
end

View file

@ -34,6 +34,26 @@ RSpec.describe Api::V2::SearchController do
expect(body_as_json[:accounts].pluck(:id)).to contain_exactly(bob.id.to_s, ana.id.to_s, tom.id.to_s)
end
context 'with truthy `resolve`' do
let(:params) { { q: 'test1', resolve: '1' } }
it 'returns http unauthorized' do
get :index, params: params
expect(response).to have_http_status(200)
end
end
context 'with `offset`' do
let(:params) { { q: 'test1', offset: 1 } }
it 'returns http unauthorized' do
get :index, params: params
expect(response).to have_http_status(200)
end
end
context 'with following=true' do
let(:params) { { q: 'test', type: 'accounts', following: 'true' } }
@ -48,6 +68,26 @@ RSpec.describe Api::V2::SearchController do
end
end
end
context 'when search raises syntax error' do
before { allow(Search).to receive(:new).and_raise(Mastodon::SyntaxError) }
it 'returns http unprocessable_entity' do
get :index, params: params
expect(response).to have_http_status(422)
end
end
context 'when search raises not found error' do
before { allow(Search).to receive(:new).and_raise(ActiveRecord::RecordNotFound) }
it 'returns http not_found' do
get :index, params: params
expect(response).to have_http_status(404)
end
end
end
end
@ -59,6 +99,12 @@ RSpec.describe Api::V2::SearchController do
get :index, params: search_params
end
context 'without a `q` param' do
it 'returns http bad_request' do
expect(response).to have_http_status(400)
end
end
context 'with a `q` shorter than 5 characters' do
let(:search_params) { { q: 'test' } }
@ -79,6 +125,7 @@ RSpec.describe Api::V2::SearchController do
it 'returns http unauthorized' do
expect(response).to have_http_status(401)
expect(response.body).to match('resolve remote resources')
end
end
@ -87,6 +134,7 @@ RSpec.describe Api::V2::SearchController do
it 'returns http unauthorized' do
expect(response).to have_http_status(401)
expect(response.body).to match('pagination is not supported')
end
end
end

View file

@ -2,9 +2,9 @@
require 'rails_helper'
describe RateLimitHeaders do
describe Api::RateLimitHeaders do
controller(ApplicationController) do
include RateLimitHeaders
include Api::RateLimitHeaders
def show
head 200

View file

@ -2,9 +2,9 @@
require 'rails_helper'
describe ExportControllerConcern do
describe Settings::ExportControllerConcern do
controller(ApplicationController) do
include ExportControllerConcern
include Settings::ExportControllerConcern
def index
send_export_file

View file

@ -20,37 +20,30 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
[true, false].each do |with_otp_secret|
let(:user) { Fabricate(:user, email: 'local-part@domain', otp_secret: with_otp_secret ? 'oldotpsecret' : nil) }
describe 'GET #new' do
context 'when signed in and a new otp secret has been set in the session' do
subject do
sign_in user, scope: :user
get :new, session: { challenge_passed_at: Time.now.utc, new_otp_secret: 'thisisasecretforthespecofnewview' }
context 'when signed in' do
before { sign_in user, scope: :user }
describe 'GET #new' do
context 'when a new otp secret has been set in the session' do
subject do
get :new, session: { challenge_passed_at: Time.now.utc, new_otp_secret: 'thisisasecretforthespecofnewview' }
end
include_examples 'renders :new'
end
include_examples 'renders :new'
end
it 'redirects if a new otp_secret has not been set in the session' do
get :new, session: { challenge_passed_at: Time.now.utc }
it 'redirects if not signed in' do
get :new
expect(response).to redirect_to('/auth/sign_in')
end
it 'redirects if a new otp_secret has not been set in the session' do
sign_in user, scope: :user
get :new, session: { challenge_passed_at: Time.now.utc }
expect(response).to redirect_to('/settings/otp_authentication')
end
end
describe 'POST #create' do
context 'when signed in' do
before do
sign_in user, scope: :user
expect(response).to redirect_to('/settings/otp_authentication')
end
end
describe 'POST #create' do
describe 'when form_two_factor_confirmation parameter is not provided' do
it 'raises ActionController::ParameterMissing' do
post :create, params: {}, session: { challenge_passed_at: Time.now.utc, new_otp_secret: 'thisisasecretforthespecofnewview' }
expect(response).to have_http_status(400)
end
end
@ -58,69 +51,78 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
describe 'when creation succeeds' do
let!(:otp_backup_codes) { user.generate_otp_backup_codes! }
it 'renders page with success' do
before do
prepare_user_otp_generation
prepare_user_otp_consumption
prepare_user_otp_consumption_response(true)
allow(controller).to receive(:current_user).and_return(user)
end
expect do
post :create,
params: { form_two_factor_confirmation: { otp_attempt: '123456' } },
session: { challenge_passed_at: Time.now.utc, new_otp_secret: 'thisisasecretforthespecofnewview' }
end.to change { user.reload.otp_secret }.to 'thisisasecretforthespecofnewview'
it 'renders page with success' do
expect { post_create_with_options }
.to change { user.reload.otp_secret }.to 'thisisasecretforthespecofnewview'
expect(assigns(:recovery_codes)).to eq otp_backup_codes
expect(flash[:notice]).to eq 'Two-factor authentication successfully enabled'
expect(response).to have_http_status(200)
expect(response).to render_template('settings/two_factor_authentication/recovery_codes/index')
end
def prepare_user_otp_generation
allow(user)
.to receive(:generate_otp_backup_codes!)
.and_return(otp_backup_codes)
end
def prepare_user_otp_consumption
options = { otp_secret: 'thisisasecretforthespecofnewview' }
allow(user)
.to receive(:validate_and_consume_otp!)
.with('123456', options)
.and_return(true)
end
end
describe 'when creation fails' do
subject do
options = { otp_secret: 'thisisasecretforthespecofnewview' }
allow(user)
.to receive(:validate_and_consume_otp!)
.with('123456', options)
.and_return(false)
allow(controller).to receive(:current_user).and_return(user)
expect do
post :create,
params: { form_two_factor_confirmation: { otp_attempt: '123456' } },
session: { challenge_passed_at: Time.now.utc, new_otp_secret: 'thisisasecretforthespecofnewview' }
end.to(not_change { user.reload.otp_secret })
expect { post_create_with_options }
.to(not_change { user.reload.otp_secret })
end
it 'renders the new view' do
before do
prepare_user_otp_consumption_response(false)
allow(controller).to receive(:current_user).and_return(user)
end
it 'renders page with error message' do
subject
expect(response.body).to include 'The entered code was invalid! Are server time and device time correct?'
end
include_examples 'renders :new'
end
end
context 'when not signed in' do
it 'redirects if not signed in' do
post :create, params: { form_two_factor_confirmation: { otp_attempt: '123456' } }
expect(response).to redirect_to('/auth/sign_in')
private
def post_create_with_options
post :create,
params: { form_two_factor_confirmation: { otp_attempt: '123456' } },
session: { challenge_passed_at: Time.now.utc, new_otp_secret: 'thisisasecretforthespecofnewview' }
end
def prepare_user_otp_generation
allow(user)
.to receive(:generate_otp_backup_codes!)
.and_return(otp_backup_codes)
end
def prepare_user_otp_consumption_response(result)
options = { otp_secret: 'thisisasecretforthespecofnewview' }
allow(user)
.to receive(:validate_and_consume_otp!)
.with('123456', options)
.and_return(result)
end
end
end
end
context 'when not signed in' do
it 'redirects on POST to create' do
post :create, params: { form_two_factor_confirmation: { otp_attempt: '123456' } }
expect(response).to redirect_to('/auth/sign_in')
end
it 'redirects on GET to new' do
get :new
expect(response).to redirect_to('/auth/sign_in')
end
end
end

View file

@ -59,10 +59,10 @@ describe ContentSecurityPolicy do
end
end
describe '#media_host' do
describe '#media_hosts' do
context 'when there is no configured CDN' do
it 'defaults to using the assets_host value' do
expect(subject.media_host).to eq(subject.assets_host)
expect(subject.media_hosts).to contain_exactly(subject.assets_host)
end
end
@ -74,7 +74,7 @@ describe ContentSecurityPolicy do
end
it 'uses the s3 alias host value' do
expect(subject.media_host).to eq 'https://asset-host.s3-alias.example'
expect(subject.media_hosts).to contain_exactly(subject.assets_host, 'https://asset-host.s3-alias.example')
end
end
@ -86,7 +86,7 @@ describe ContentSecurityPolicy do
end
it 'uses the s3 alias host value and preserves the path' do
expect(subject.media_host).to eq 'https://asset-host.s3-alias.example/pathname/'
expect(subject.media_hosts).to contain_exactly(subject.assets_host, 'https://asset-host.s3-alias.example/pathname/')
end
end
@ -98,7 +98,7 @@ describe ContentSecurityPolicy do
end
it 'uses the s3 cloudfront host value' do
expect(subject.media_host).to eq 'https://asset-host.s3-cloudfront.example'
expect(subject.media_hosts).to contain_exactly(subject.assets_host, 'https://asset-host.s3-cloudfront.example')
end
end
@ -110,7 +110,7 @@ describe ContentSecurityPolicy do
end
it 'uses the azure alias host value' do
expect(subject.media_host).to eq 'https://asset-host.azure-alias.example'
expect(subject.media_hosts).to contain_exactly(subject.assets_host, 'https://asset-host.azure-alias.example')
end
end
@ -122,7 +122,7 @@ describe ContentSecurityPolicy do
end
it 'uses the s3 hostname host value' do
expect(subject.media_host).to eq 'https://asset-host.s3.example'
expect(subject.media_hosts).to contain_exactly(subject.assets_host, 'https://asset-host.s3.example')
end
end
end

View file

@ -2,23 +2,22 @@
require 'rails_helper'
RSpec.describe AccountsController do
render_views
describe 'Accounts show response' do
let(:account) { Fabricate(:account) }
describe 'unapproved account check' do
context 'with an unapproved account' do
before { account.user.update(approved: false) }
it 'returns http not found' do
%w(html json rss).each do |format|
get :show, params: { username: account.username, format: format }
get short_account_path(username: account.username), as: format
expect(response).to have_http_status(404)
end
end
end
describe 'permanently suspended account check' do
context 'with a permanently suspended account' do
before do
account.suspend!
account.deletion_request.destroy
@ -26,25 +25,26 @@ RSpec.describe AccountsController do
it 'returns http gone' do
%w(html json rss).each do |format|
get :show, params: { username: account.username, format: format }
get short_account_path(username: account.username), as: format
expect(response).to have_http_status(410)
end
end
end
describe 'temporarily suspended account check' do
context 'with a temporarily suspended account' do
before { account.suspend! }
it 'returns appropriate http response code' do
{ html: 403, json: 200, rss: 403 }.each do |format, code|
get :show, params: { username: account.username, format: format }
get short_account_path(username: account.username), as: format
expect(response).to have_http_status(code)
end
end
end
describe 'GET #show' do
describe 'GET to short username paths' do
context 'with existing statuses' do
let!(:status) { Fabricate(:status, account: account) }
let!(:status_reply) { Fabricate(:status, account: account, thread: Fabricate(:status)) }
@ -66,17 +66,17 @@ RSpec.describe AccountsController do
shared_examples 'common HTML response' do
it 'returns a standard HTML response', :aggregate_failures do
expect(response).to have_http_status(200)
expect(response)
.to have_http_status(200)
.and render_template(:show)
expect(response.headers['Link'].to_s).to include ActivityPub::TagManager.instance.uri_for(account)
expect(response).to render_template(:show)
end
end
context 'with a normal account in an HTML request' do
before do
get :show, params: { username: account.username, format: format }
get short_account_path(username: account.username), as: format
end
it_behaves_like 'common HTML response'
@ -84,8 +84,7 @@ RSpec.describe AccountsController do
context 'with replies' do
before do
allow(controller).to receive(:replies_requested?).and_return(true)
get :show, params: { username: account.username, format: format }
get short_account_with_replies_path(username: account.username), as: format
end
it_behaves_like 'common HTML response'
@ -93,8 +92,7 @@ RSpec.describe AccountsController do
context 'with media' do
before do
allow(controller).to receive(:media_requested?).and_return(true)
get :show, params: { username: account.username, format: format }
get short_account_media_path(username: account.username), as: format
end
it_behaves_like 'common HTML response'
@ -106,9 +104,8 @@ RSpec.describe AccountsController do
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 }
get short_account_tag_path(username: account.username, tag: tag), as: format
end
it_behaves_like 'common HTML response'
@ -117,21 +114,25 @@ RSpec.describe AccountsController do
context 'with JSON' do
let(:authorized_fetch_mode) { false }
let(:format) { 'json' }
let(:headers) { { 'ACCEPT' => 'application/json' } }
before do
allow(controller).to receive(:authorized_fetch_mode?).and_return(authorized_fetch_mode)
around do |example|
ClimateControl.modify AUTHORIZED_FETCH: authorized_fetch_mode.to_s do
example.run
end
end
context 'with a normal account in a JSON request' do
before do
get :show, params: { username: account.username, format: format }
get short_account_path(username: account.username), headers: headers
end
it 'returns a JSON version of the account', :aggregate_failures do
expect(response).to have_http_status(200)
expect(response.media_type).to eq 'application/activity+json'
expect(response)
.to have_http_status(200)
.and have_attributes(
media_type: eq('application/activity+json')
)
expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
end
@ -152,13 +153,15 @@ RSpec.describe AccountsController do
before do
sign_in(user)
get :show, params: { username: account.username, format: format }
get short_account_path(username: account.username), headers: headers.merge({ 'Cookie' => '123' })
end
it 'returns a private JSON version of the account', :aggregate_failures do
expect(response).to have_http_status(200)
expect(response.media_type).to eq 'application/activity+json'
expect(response)
.to have_http_status(200)
.and have_attributes(
media_type: eq('application/activity+json')
)
expect(response.headers['Cache-Control']).to include 'private'
@ -170,14 +173,15 @@ RSpec.describe AccountsController do
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
before do
allow(controller).to receive(:signed_request_actor).and_return(remote_account)
get :show, params: { username: account.username, format: format }
get short_account_path(username: account.username), headers: headers, sign_with: remote_account
end
it 'returns a JSON version of the account', :aggregate_failures do
expect(response).to have_http_status(200)
expect(response.media_type).to eq 'application/activity+json'
expect(response)
.to have_http_status(200)
.and have_attributes(
media_type: eq('application/activity+json')
)
expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
end
@ -188,12 +192,13 @@ RSpec.describe AccountsController do
let(:authorized_fetch_mode) { true }
it 'returns a private signature JSON version of the account', :aggregate_failures do
expect(response).to have_http_status(200)
expect(response.media_type).to eq 'application/activity+json'
expect(response)
.to have_http_status(200)
.and have_attributes(
media_type: eq('application/activity+json')
)
expect(response.headers['Cache-Control']).to include 'private'
expect(response.headers['Vary']).to include 'Signature'
expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
@ -207,60 +212,58 @@ RSpec.describe AccountsController do
context 'with a normal account in an RSS request' do
before do
get :show, params: { username: account.username, format: format }
get short_account_path(username: account.username, format: format)
end
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
it 'responds with correct statuses', :aggregate_failures do
expect(response).to have_http_status(200)
expect(response.body).to include_status_tag(status_media)
expect(response.body).to include_status_tag(status_self_reply)
expect(response.body).to include_status_tag(status)
expect(response.body).to_not include_status_tag(status_direct)
expect(response.body).to_not include_status_tag(status_private)
expect(response.body).to_not include_status_tag(status_reblog.reblog)
expect(response.body).to_not include_status_tag(status_reply)
expect(response.body).to include(status_tag_for(status_media))
expect(response.body).to include(status_tag_for(status_self_reply))
expect(response.body).to include(status_tag_for(status))
expect(response.body).to_not include(status_tag_for(status_direct))
expect(response.body).to_not include(status_tag_for(status_private))
expect(response.body).to_not include(status_tag_for(status_reblog.reblog))
expect(response.body).to_not include(status_tag_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 }
get short_account_with_replies_path(username: account.username, format: format)
end
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
it 'responds with correct statuses with replies', :aggregate_failures do
expect(response).to have_http_status(200)
expect(response.body).to include_status_tag(status_media)
expect(response.body).to include_status_tag(status_reply)
expect(response.body).to include_status_tag(status_self_reply)
expect(response.body).to include_status_tag(status)
expect(response.body).to_not include_status_tag(status_direct)
expect(response.body).to_not include_status_tag(status_private)
expect(response.body).to_not include_status_tag(status_reblog.reblog)
expect(response.body).to include(status_tag_for(status_media))
expect(response.body).to include(status_tag_for(status_reply))
expect(response.body).to include(status_tag_for(status_self_reply))
expect(response.body).to include(status_tag_for(status))
expect(response.body).to_not include(status_tag_for(status_direct))
expect(response.body).to_not include(status_tag_for(status_private))
expect(response.body).to_not include(status_tag_for(status_reblog.reblog))
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 }
get short_account_media_path(username: account.username, format: format)
end
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
it 'responds with correct statuses with media', :aggregate_failures do
expect(response).to have_http_status(200)
expect(response.body).to include_status_tag(status_media)
expect(response.body).to_not include_status_tag(status_direct)
expect(response.body).to_not include_status_tag(status_private)
expect(response.body).to_not include_status_tag(status_reblog.reblog)
expect(response.body).to_not include_status_tag(status_reply)
expect(response.body).to_not include_status_tag(status_self_reply)
expect(response.body).to_not include_status_tag(status)
expect(response.body).to include(status_tag_for(status_media))
expect(response.body).to_not include(status_tag_for(status_direct))
expect(response.body).to_not include(status_tag_for(status_private))
expect(response.body).to_not include(status_tag_for(status_reblog.reblog))
expect(response.body).to_not include(status_tag_for(status_reply))
expect(response.body).to_not include(status_tag_for(status_self_reply))
expect(response.body).to_not include(status_tag_for(status))
end
end
@ -270,30 +273,29 @@ RSpec.describe AccountsController do
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 }
get short_account_tag_path(username: account.username, tag: tag, format: format)
end
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
it 'responds with correct statuses with a tag', :aggregate_failures do
expect(response).to have_http_status(200)
expect(response.body).to include_status_tag(status_tag)
expect(response.body).to_not include_status_tag(status_direct)
expect(response.body).to_not include_status_tag(status_media)
expect(response.body).to_not include_status_tag(status_private)
expect(response.body).to_not include_status_tag(status_reblog.reblog)
expect(response.body).to_not include_status_tag(status_reply)
expect(response.body).to_not include_status_tag(status_self_reply)
expect(response.body).to_not include_status_tag(status)
expect(response.body).to include(status_tag_for(status_tag))
expect(response.body).to_not include(status_tag_for(status_direct))
expect(response.body).to_not include(status_tag_for(status_media))
expect(response.body).to_not include(status_tag_for(status_private))
expect(response.body).to_not include(status_tag_for(status_reblog.reblog))
expect(response.body).to_not include(status_tag_for(status_reply))
expect(response.body).to_not include(status_tag_for(status_self_reply))
expect(response.body).to_not include(status_tag_for(status))
end
end
end
end
end
def include_status_tag(status)
include ActivityPub::TagManager.instance.url_for(status)
def status_tag_for(status)
ActivityPub::TagManager.instance.url_for(status)
end
end

View file

@ -12,15 +12,15 @@ describe 'Content-Security-Policy' do
"default-src 'none'",
"frame-ancestors 'none'",
"font-src 'self' https://cb6e6126.ngrok.io",
"img-src 'self' https: data: blob: https://cb6e6126.ngrok.io",
"img-src 'self' data: blob: https://cb6e6126.ngrok.io",
"style-src 'self' https://cb6e6126.ngrok.io 'nonce-ZbA+JmE7+bK8F5qvADZHuQ=='",
"media-src 'self' https: data: https://cb6e6126.ngrok.io",
"media-src 'self' data: https://cb6e6126.ngrok.io",
"frame-src 'self' https:",
"manifest-src 'self' https://cb6e6126.ngrok.io",
"form-action 'self'",
"child-src 'self' blob: https://cb6e6126.ngrok.io",
"worker-src 'self' blob: https://cb6e6126.ngrok.io",
"connect-src 'self' data: blob: https://cb6e6126.ngrok.io https://cb6e6126.ngrok.io ws://localhost:4000",
"connect-src 'self' data: blob: https://cb6e6126.ngrok.io ws://localhost:4000",
"script-src 'self' https://cb6e6126.ngrok.io 'wasm-unsafe-eval'"
)
end

View file

@ -384,7 +384,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do
end
it 'updates the existing media attachment in-place' do
media_attachment = status.media_attachments.reload.first
media_attachment = status.media_attachments.ordered.reload.first
expect(media_attachment).to_not be_nil
expect(media_attachment.remote_url).to eq 'https://example.com/foo.png'

View file

@ -1483,11 +1483,11 @@ __metadata:
linkType: hard
"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.22.3, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
version: 7.23.4
resolution: "@babel/runtime@npm:7.23.4"
version: 7.23.5
resolution: "@babel/runtime@npm:7.23.5"
dependencies:
regenerator-runtime: "npm:^0.14.0"
checksum: db2bf183cd0119599b903ca51ca0aeea8e0ab478a16be1aae10dd90473ed614159d3e5adfdd8f8f3d840402428ce0d90b5c01aae95da9e45a2dd83e02d85ca27
checksum: ca679cc91bb7e424bc2db87bb58cc3b06ade916b9adb21fbbdc43e54cdaacb3eea201ceba2a0464b11d2eb65b9fe6a6ffcf4d7521fa52994f19be96f1af14788
languageName: node
linkType: hard
@ -10640,8 +10640,8 @@ __metadata:
linkType: hard
"jsdom@npm:^23.0.0":
version: 23.0.0
resolution: "jsdom@npm:23.0.0"
version: 23.0.1
resolution: "jsdom@npm:23.0.1"
dependencies:
cssstyle: "npm:^3.0.0"
data-urls: "npm:^5.0.0"
@ -10665,11 +10665,11 @@ __metadata:
ws: "npm:^8.14.2"
xml-name-validator: "npm:^5.0.0"
peerDependencies:
canvas: ^3.0.0
canvas: ^2.11.2
peerDependenciesMeta:
canvas:
optional: true
checksum: 2c876a02de49e0ed6b667a4eb9b08b8e76ac189a5571ff97791cc9564e713259314deea6d657cc7f59fc30af41b900e7d833c95017e576dfcaf25f32565722af
checksum: 13b2b3693ccb40215d1cce77bac7a295414ee4c0a06e30167f8087c9867145ba23dbd592bd95a801cadd7b3698bfd20b9c3f2c26fd8422607f22609ed2e404ef
languageName: node
linkType: hard