mirror of
https://github.com/mastodon/mastodon.git
synced 2025-09-05 17:31:12 +00:00
Change public self-quotes of private posts to be disallowed (#35975)
Some checks failed
Check i18n / check-i18n (push) Waiting to run
Chromatic / Run Chromatic (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
JavaScript Linting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
JavaScript Testing / test (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Crowdin / Upload translations / upload-translations (push) Has been cancelled
Some checks failed
Check i18n / check-i18n (push) Waiting to run
Chromatic / Run Chromatic (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
JavaScript Linting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
JavaScript Testing / test (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Crowdin / Upload translations / upload-translations (push) Has been cancelled
This commit is contained in:
parent
5d9a9c76fb
commit
75fca715e9
|
@ -89,6 +89,24 @@ const selectStatusPolicy = createAppSelector(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const selectDisablePublicVisibilities = createAppSelector(
|
||||||
|
[
|
||||||
|
(state) => state.statuses,
|
||||||
|
(_state, statusId?: string) => !!statusId,
|
||||||
|
(state) => state.compose.get('quoted_status_id') as string | null,
|
||||||
|
],
|
||||||
|
(statuses, isEditing, statusId) => {
|
||||||
|
if (isEditing || !statusId) return false;
|
||||||
|
|
||||||
|
const status = statuses.get(statusId);
|
||||||
|
if (!status) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status.get('visibility') === 'private';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
({ onClose, onChange, statusId }, _ref) => {
|
({ onClose, onChange, statusId }, _ref) => {
|
||||||
|
@ -110,24 +128,12 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||||
const disableVisibility = !!statusId;
|
const disableVisibility = !!statusId;
|
||||||
const disableQuotePolicy =
|
const disableQuotePolicy =
|
||||||
visibility === 'private' || visibility === 'direct';
|
visibility === 'private' || visibility === 'direct';
|
||||||
|
const disablePublicVisibilities: boolean = useAppSelector(
|
||||||
|
selectDisablePublicVisibilities,
|
||||||
|
);
|
||||||
|
|
||||||
const visibilityItems = useMemo<SelectItem<StatusVisibility>[]>(
|
const visibilityItems = useMemo<SelectItem<StatusVisibility>[]>(() => {
|
||||||
() => [
|
const items: SelectItem<StatusVisibility>[] = [
|
||||||
{
|
|
||||||
value: 'public',
|
|
||||||
text: intl.formatMessage(privacyMessages.public_short),
|
|
||||||
meta: intl.formatMessage(privacyMessages.public_long),
|
|
||||||
icon: 'globe',
|
|
||||||
iconComponent: PublicIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'unlisted',
|
|
||||||
text: intl.formatMessage(privacyMessages.unlisted_short),
|
|
||||||
meta: intl.formatMessage(privacyMessages.unlisted_long),
|
|
||||||
extra: intl.formatMessage(privacyMessages.unlisted_extra),
|
|
||||||
icon: 'unlock',
|
|
||||||
iconComponent: QuietTimeIcon,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
value: 'private',
|
value: 'private',
|
||||||
text: intl.formatMessage(privacyMessages.private_short),
|
text: intl.formatMessage(privacyMessages.private_short),
|
||||||
|
@ -142,9 +148,30 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||||
icon: 'at',
|
icon: 'at',
|
||||||
iconComponent: AlternateEmailIcon,
|
iconComponent: AlternateEmailIcon,
|
||||||
},
|
},
|
||||||
],
|
];
|
||||||
[intl],
|
|
||||||
);
|
if (!disablePublicVisibilities) {
|
||||||
|
items.unshift(
|
||||||
|
{
|
||||||
|
value: 'public',
|
||||||
|
text: intl.formatMessage(privacyMessages.public_short),
|
||||||
|
meta: intl.formatMessage(privacyMessages.public_long),
|
||||||
|
icon: 'globe',
|
||||||
|
iconComponent: PublicIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'unlisted',
|
||||||
|
text: intl.formatMessage(privacyMessages.unlisted_short),
|
||||||
|
meta: intl.formatMessage(privacyMessages.unlisted_long),
|
||||||
|
extra: intl.formatMessage(privacyMessages.unlisted_extra),
|
||||||
|
icon: 'unlock',
|
||||||
|
iconComponent: QuietTimeIcon,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}, [intl, disablePublicVisibilities]);
|
||||||
const quoteItems = useMemo<SelectItem<ApiQuotePolicy>[]>(
|
const quoteItems = useMemo<SelectItem<ApiQuotePolicy>[]>(
|
||||||
() => [
|
() => [
|
||||||
{ value: 'public', text: intl.formatMessage(messages.quotePublic) },
|
{ value: 'public', text: intl.formatMessage(messages.quotePublic) },
|
||||||
|
@ -236,6 +263,14 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
{!statusId && disablePublicVisibilities && (
|
||||||
|
<p className='visibility-dropdown__helper'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='visibility_modal.helper.privacy_private_self_quote'
|
||||||
|
defaultMessage='Self-quotes of private posts cannot be made public.'
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label
|
<label
|
||||||
|
|
|
@ -989,6 +989,7 @@
|
||||||
"visibility_modal.header": "Visibility and interaction",
|
"visibility_modal.header": "Visibility and interaction",
|
||||||
"visibility_modal.helper.direct_quoting": "Private mentions authored on Mastodon can't be quoted by others.",
|
"visibility_modal.helper.direct_quoting": "Private mentions authored on Mastodon can't be quoted by others.",
|
||||||
"visibility_modal.helper.privacy_editing": "Published posts cannot change their visibility.",
|
"visibility_modal.helper.privacy_editing": "Published posts cannot change their visibility.",
|
||||||
|
"visibility_modal.helper.privacy_private_self_quote": "Self-quotes of private posts cannot be made public.",
|
||||||
"visibility_modal.helper.private_quoting": "Follower-only posts authored on Mastodon can't be quoted by others.",
|
"visibility_modal.helper.private_quoting": "Follower-only posts authored on Mastodon can't be quoted by others.",
|
||||||
"visibility_modal.helper.unlisted_quoting": "When people quote you, their post will also be hidden from trending timelines.",
|
"visibility_modal.helper.unlisted_quoting": "When people quote you, their post will also be hidden from trending timelines.",
|
||||||
"visibility_modal.instructions": "Control who can interact with this post. You can also apply settings to all future posts by navigating to <link>Preferences > Posting defaults</link>.",
|
"visibility_modal.instructions": "Control who can interact with this post. You can also apply settings to all future posts by navigating to <link>Preferences > Posting defaults</link>.",
|
||||||
|
|
|
@ -334,7 +334,8 @@ export const composeReducer = (state = initialState, action) => {
|
||||||
return state
|
return state
|
||||||
.set('quoted_status_id', status.get('id'))
|
.set('quoted_status_id', status.get('id'))
|
||||||
.set('spoiler', status.get('sensitive'))
|
.set('spoiler', status.get('sensitive'))
|
||||||
.set('spoiler_text', status.get('spoiler_text'));
|
.set('spoiler_text', status.get('spoiler_text'))
|
||||||
|
.update('privacy', (visibility) => ['public', 'unlisted'].includes(visibility) && status.get('visibility') === 'private' ? 'private' : visibility);
|
||||||
} else if (quoteComposeCancel.match(action)) {
|
} else if (quoteComposeCancel.match(action)) {
|
||||||
return state.set('quoted_status_id', null);
|
return state.set('quoted_status_id', null);
|
||||||
} else if (setComposeQuotePolicy.match(action)) {
|
} else if (setComposeQuotePolicy.match(action)) {
|
||||||
|
|
|
@ -27,7 +27,7 @@ module Status::InteractionPolicyConcern
|
||||||
|
|
||||||
# Returns `:automatic`, `:manual`, `:unknown` or `:denied`
|
# Returns `:automatic`, `:manual`, `:unknown` or `:denied`
|
||||||
def quote_policy_for_account(other_account, preloaded_relations: {})
|
def quote_policy_for_account(other_account, preloaded_relations: {})
|
||||||
return :denied if other_account.nil?
|
return :denied if other_account.nil? || direct_visibility?
|
||||||
|
|
||||||
following_author = nil
|
following_author = nil
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,7 @@ class PostStatusService < BaseService
|
||||||
@text = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present?
|
@text = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present?
|
||||||
@visibility = @options[:visibility] || @account.user&.setting_default_privacy
|
@visibility = @options[:visibility] || @account.user&.setting_default_privacy
|
||||||
@visibility = :unlisted if @visibility&.to_sym == :public && @account.silenced?
|
@visibility = :unlisted if @visibility&.to_sym == :public && @account.silenced?
|
||||||
|
@visibility = :private if @quoted_status&.private_visibility?
|
||||||
@scheduled_at = @options[:scheduled_at]&.to_datetime
|
@scheduled_at = @options[:scheduled_at]&.to_datetime
|
||||||
@scheduled_at = nil if scheduled_in_the_past?
|
@scheduled_at = nil if scheduled_in_the_past?
|
||||||
rescue ArgumentError
|
rescue ArgumentError
|
||||||
|
|
|
@ -88,10 +88,10 @@ RSpec.describe StatusPolicy, type: :model do
|
||||||
|
|
||||||
context 'with the permission of quote?' do
|
context 'with the permission of quote?' do
|
||||||
permissions :quote? do
|
permissions :quote? do
|
||||||
it 'grants access when direct and account is viewer' do
|
it 'does not grant access when direct and account is viewer' do
|
||||||
status.visibility = :direct
|
status.visibility = :direct
|
||||||
|
|
||||||
expect(subject).to permit(status.account, status)
|
expect(subject).to_not permit(status.account, status)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not grant access access when direct and viewer is mentioned but not explicitly allowed' do
|
it 'does not grant access access when direct and viewer is mentioned but not explicitly allowed' do
|
||||||
|
|
|
@ -305,6 +305,14 @@ RSpec.describe PostStatusService do
|
||||||
.to enqueue_sidekiq_job(ActivityPub::QuoteRequestWorker)
|
.to enqueue_sidekiq_job(ActivityPub::QuoteRequestWorker)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'correctly downgrades visibility for private self-quotes' do
|
||||||
|
account = Fabricate(:account)
|
||||||
|
quoted_status = Fabricate(:status, account: account, visibility: :private)
|
||||||
|
|
||||||
|
status = subject.call(account, text: 'test', quoted_status: quoted_status)
|
||||||
|
expect(status).to be_private_visibility
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns existing status when used twice with idempotency key' do
|
it 'returns existing status when used twice with idempotency key' do
|
||||||
account = Fabricate(:account)
|
account = Fabricate(:account)
|
||||||
status1 = subject.call(account, text: 'test', idempotency: 'meepmeep')
|
status1 = subject.call(account, text: 'test', idempotency: 'meepmeep')
|
||||||
|
|
Loading…
Reference in New Issue
Block a user