From 2cb2fe1495487ee22bdee69e9a798b22bbbb859e Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Thu, 3 Aug 2023 17:10:07 +0900 Subject: [PATCH 1/7] Add invitation expire period setting for Admin --- app/lib/vacuum/invites_vacuum.rb | 25 +++++++++++++++++++++++ app/models/form/admin_settings.rb | 5 +++++ app/models/invite_retention_policy.rb | 25 +++++++++++++++++++++++ app/workers/scheduler/vacuum_scheduler.rb | 12 +++++++++++ 4 files changed, 67 insertions(+) create mode 100644 app/lib/vacuum/invites_vacuum.rb create mode 100644 app/models/invite_retention_policy.rb diff --git a/app/lib/vacuum/invites_vacuum.rb b/app/lib/vacuum/invites_vacuum.rb new file mode 100644 index 0000000000..8bfbcb6817 --- /dev/null +++ b/app/lib/vacuum/invites_vacuum.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Vacuum::InvitesVacuum + def initialize(retention_period, max_uses) + @retention_period = retention_period + @max_uses = max_uses + end + + def perform + expire_invites! if retention_period? + end + + private + + def expire_invites! + invites = Invite.where('created_at < ?', retention_period.ago) + invites = if max_uses.nil? + invites.where(max_uses: nil) + else + invites.where('max_uses > ? OR max_uses IS NULL', max_uses) + end + + invites.reorder(nil).in_batches(&:expire!) + end +end diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index a6be55fd7b..ca1b2695c3 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -32,6 +32,8 @@ class Form::AdminSettings media_cache_retention_period content_cache_retention_period backups_retention_period + invite_expiration_period + invite_max_uses status_page_url captcha_enabled ).freeze @@ -40,6 +42,8 @@ class Form::AdminSettings media_cache_retention_period content_cache_retention_period backups_retention_period + invite_expiration_period + invite_max_uses ).freeze BOOLEAN_KEYS = %i( @@ -70,6 +74,7 @@ class Form::AdminSettings validates :show_domain_blocks, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks) } validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks_rationale) } validates :media_cache_retention_period, :content_cache_retention_period, :backups_retention_period, numericality: { only_integer: true }, allow_blank: true, if: -> { defined?(@media_cache_retention_period) || defined?(@content_cache_retention_period) || defined?(@backups_retention_period) } + validates :invite_expiration_period, :invite_max_uses, numericality: { only_integer: true }, allow_blank: true, if: -> { defined?(@invite_expiration_period) || defined?(@invite_max_uses) } validates :site_short_description, length: { maximum: 200 }, if: -> { defined?(@site_short_description) } validates :status_page_url, url: true, allow_blank: true validate :validate_site_uploads diff --git a/app/models/invite_retention_policy.rb b/app/models/invite_retention_policy.rb new file mode 100644 index 0000000000..ff06f0e26b --- /dev/null +++ b/app/models/invite_retention_policy.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class InviteRetentionPolicy + def self.current + new + end + + def invite_retention_period + retention_period Setting.invite_retention_period + end + + def invite_max_uses + max_uses Setting.invite_max_uses + end + + private + + def retention_period(value) + value.days if value.is_a?(Integer) && value.positive? + end + + def max_uses(value) + value if value.is_a?(Integer) && value.positive? + end +end diff --git a/app/workers/scheduler/vacuum_scheduler.rb b/app/workers/scheduler/vacuum_scheduler.rb index 1c9a2aabe3..8562e36f56 100644 --- a/app/workers/scheduler/vacuum_scheduler.rb +++ b/app/workers/scheduler/vacuum_scheduler.rb @@ -25,6 +25,7 @@ class Scheduler::VacuumScheduler applications_vacuum, feeds_vacuum, imports_vacuum, + invites_vacuum, ] end @@ -60,7 +61,18 @@ class Scheduler::VacuumScheduler Vacuum::ApplicationsVacuum.new end + def invites_vacuum + Vacuum::InvitesVacuum.new( + invites_retention_policy.invite_retention_period, + invites_retention_policy.invite_max_uses + ) + end + def content_retention_policy ContentRetentionPolicy.current end + + def invites_retention_policy + InvitesRetentionPolicy.current + end end From 64db68de15d461e5a83dc2dcce3c1533616a651e Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Thu, 3 Aug 2023 17:52:56 +0900 Subject: [PATCH 2/7] Add admin settings for force invitation link expiration --- app/views/admin/settings/registrations/show.html.haml | 4 ++++ config/locales/simple_form.en.yml | 4 ++++ config/locales/simple_form.ko.yml | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/app/views/admin/settings/registrations/show.html.haml b/app/views/admin/settings/registrations/show.html.haml index e06385bc81..951907c3ad 100644 --- a/app/views/admin/settings/registrations/show.html.haml +++ b/app/views/admin/settings/registrations/show.html.haml @@ -20,6 +20,10 @@ .fields-row__column.fields-row__column-6.fields-group = f.input :require_invite_text, as: :boolean, wrapper: :with_label, disabled: !approved_registrations? + .fields-group + = f.input :invite_expiration_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } + = f.input :invite_max_uses, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } + - if captcha_available? .fields-group = f.input :captcha_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.captcha_enabled.title'), hint: t('admin.settings.captcha_enabled.desc_html') diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 4307d65e9b..4f0542a0cc 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -83,6 +83,8 @@ en: closed_registrations_message: Displayed when sign-ups are closed content_cache_retention_period: All posts and boosts from other servers will be deleted after the specified number of days. Some posts may not be recoverable. All related bookmarks, favourites and boosts will also be lost and impossible to undo. custom_css: You can apply custom styles on the web version of Mastodon. + invite_expiration_period: If set to a positive number, invitation links will expire after the specified number of days. + invite_max_uses: If set, invite links with fewer than the specified number of uses will not be forced to expire. mascot: Overrides the illustration in the advanced web interface. media_cache_retention_period: Downloaded media files will be deleted after the specified number of days when set to a positive value, and re-downloaded on demand. peers_api_enabled: A list of domain names this server has encountered in the fediverse. No data is included here about whether you federate with a given server, just that your server knows about it. This is used by services that collect statistics on federation in a general sense. @@ -242,6 +244,8 @@ en: closed_registrations_message: Custom message when sign-ups are not available content_cache_retention_period: Content cache retention period custom_css: Custom CSS + invite_expiration_period: Invitation link force expiration period + invite_max_uses: Max uses threshold for force expiration of invitation link mascot: Custom mascot (legacy) media_cache_retention_period: Media cache retention period peers_api_enabled: Publish list of discovered servers in the API diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml index 4c51117a13..c2da80332a 100644 --- a/config/locales/simple_form.ko.yml +++ b/config/locales/simple_form.ko.yml @@ -83,6 +83,8 @@ ko: closed_registrations_message: 새 가입을 차단했을 때 표시됩니다 content_cache_retention_period: 다른 서버의 게시물과 부스트들은 지정한 일수가 지나면 삭제될 것입니다. 몇몇 게시물들은 복구가 불가능할 것입니다. 관련된 북마크, 좋아요, 부스트 또한 잃어버릴 것이며 취소도 할 수 없습니다. custom_css: 사용자 지정 스타일을 웹 버전의 마스토돈에 지정할 수 있습니다. + invite_expiration_period: 양수로 설정된 경우 지정된 일수가 지난 초대 링크들은 강제로 만료될 것입니다. + invite_max_uses: 설정한 경우 지정한 수 이하의 최대 사용횟수를 가진 초대 링크는 강제로 만료되지 않을 것입니다. mascot: 고급 웹 인터페이스의 그림을 대체합니다. media_cache_retention_period: 양수로 설정된 경우 다운로드된 미디어 파일들은 지정된 일수가 지나면 삭제될 것이고 필요할 때 다시 다운로드 될 것입니다. peers_api_enabled: 이 서버가 연합우주에서 만났던 서버들에 대한 도메인 네임의 목록입니다. 해당 서버와 어떤 연합을 했는지에 대한 정보는 전혀 포함되지 않고, 단순히 그 서버를 알고 있는지에 대한 것입니다. 이것은 일반적으로 연합에 대한 통계를 수집할 때 사용됩니다. @@ -242,6 +244,8 @@ ko: closed_registrations_message: 가입이 불가능 할 때의 사용자 지정 메시지 content_cache_retention_period: 콘텐츠 캐시 유지 기한 custom_css: 사용자 정의 CSS + invite_expiration_period: 초대 링크 강제 만료 기한 + invite_max_uses: 초대 링크 강제 만료 최대 사용횟수 임계값 mascot: 사용자 정의 마스코트 (legacy) media_cache_retention_period: 미디어 캐시 유지 기한 peers_api_enabled: API에 발견 된 서버들의 목록 발행 From 560503edcdf23c26d2b7aaf5d8ee2adac5789372 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Thu, 3 Aug 2023 18:35:13 +0900 Subject: [PATCH 3/7] Fix invites vacuum --- app/lib/vacuum/invites_vacuum.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/lib/vacuum/invites_vacuum.rb b/app/lib/vacuum/invites_vacuum.rb index 8bfbcb6817..82eddee267 100644 --- a/app/lib/vacuum/invites_vacuum.rb +++ b/app/lib/vacuum/invites_vacuum.rb @@ -7,17 +7,17 @@ class Vacuum::InvitesVacuum end def perform - expire_invites! if retention_period? + expire_invites! if @retention_period.present? end private def expire_invites! - invites = Invite.where('created_at < ?', retention_period.ago) - invites = if max_uses.nil? - invites.where(max_uses: nil) + invites = Invite.where('created_at < ?', @retention_period.ago) + invites = if @max_uses.present? + invites.where('max_uses > ? OR max_uses IS NULL', @max_uses) else - invites.where('max_uses > ? OR max_uses IS NULL', max_uses) + invites.where(max_uses: nil) end invites.reorder(nil).in_batches(&:expire!) From 298e11e24c401559b9f0ac477b6f009b0db6b3b7 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Thu, 3 Aug 2023 18:36:00 +0900 Subject: [PATCH 4/7] Add test for Vacuum::InvitesVacuum --- spec/lib/vacuum/invites_vacuum_spec.rb | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 spec/lib/vacuum/invites_vacuum_spec.rb diff --git a/spec/lib/vacuum/invites_vacuum_spec.rb b/spec/lib/vacuum/invites_vacuum_spec.rb new file mode 100644 index 0000000000..ab1f1edebc --- /dev/null +++ b/spec/lib/vacuum/invites_vacuum_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Vacuum::InvitesVacuum do + subject { described_class.new(retention_period, retention_max_uses) } + + let(:retention_period) { 7.days } + let(:retention_max_uses) { 10 } + + describe '#perform' do + invite_unlimited = Fabricate(:invite, max_uses: nil, expires_at: nil) + invite_huge_max_uses = Fabricate(:invite, max_uses: 100, expires_at: nil) + invite_small_max_uses = Fabricate(:invite, max_uses: 2, expires_at: nil) + invite_will_expires = Fabricate(:invite, max_uses: nil, created_at: 1.hour.ago, expires_at: 1.hour.from_now) + + before do + subject.perform + end + + it 'expires unlimited invitation link' do + expect(invite_unlimited.expired?).to be true + end + + it 'expires invitation link that have huge max uses' do + expect(invite_huge_max_uses.expired?).to be true + end + + it 'does not expires invitation link that have small max uses' do + expect(invite_small_max_uses.expired?).to be false + end + + it 'expires invitation link that will expire' do + expect(invite_will_expires.expired?).to be true + end + end +end From 5b7e3996c5f2adf1c857ea92630b8e103ebff2a9 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Thu, 3 Aug 2023 19:39:09 +0900 Subject: [PATCH 5/7] Fix typo on vacuum scheduler --- app/workers/scheduler/vacuum_scheduler.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/workers/scheduler/vacuum_scheduler.rb b/app/workers/scheduler/vacuum_scheduler.rb index 8562e36f56..cb1ce74f09 100644 --- a/app/workers/scheduler/vacuum_scheduler.rb +++ b/app/workers/scheduler/vacuum_scheduler.rb @@ -73,6 +73,6 @@ class Scheduler::VacuumScheduler end def invites_retention_policy - InvitesRetentionPolicy.current + InviteRetentionPolicy.current end end From dbcbf0a0578299b8315faa56e53e769b0a9915ad Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Thu, 3 Aug 2023 19:41:14 +0900 Subject: [PATCH 6/7] Fix test on invites vacuum Reload was necessary --- spec/lib/vacuum/invites_vacuum_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/lib/vacuum/invites_vacuum_spec.rb b/spec/lib/vacuum/invites_vacuum_spec.rb index ab1f1edebc..1f4833e2b2 100644 --- a/spec/lib/vacuum/invites_vacuum_spec.rb +++ b/spec/lib/vacuum/invites_vacuum_spec.rb @@ -19,19 +19,19 @@ RSpec.describe Vacuum::InvitesVacuum do end it 'expires unlimited invitation link' do - expect(invite_unlimited.expired?).to be true + expect(invite_unlimited.reload.expired?).to be true end it 'expires invitation link that have huge max uses' do - expect(invite_huge_max_uses.expired?).to be true + expect(invite_huge_max_uses.reload.expired?).to be true end it 'does not expires invitation link that have small max uses' do - expect(invite_small_max_uses.expired?).to be false + expect(invite_small_max_uses.reload.expired?).to be false end it 'expires invitation link that will expire' do - expect(invite_will_expires.expired?).to be true + expect(invite_will_expires.reload.expired?).to be true end end end From 0e7ab24191dfb58d12ba28126213d570c7ec2788 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Thu, 3 Aug 2023 20:45:25 +0900 Subject: [PATCH 7/7] Fix invites vacuum and its test --- app/lib/vacuum/invites_vacuum.rb | 4 ++-- spec/lib/vacuum/invites_vacuum_spec.rb | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/lib/vacuum/invites_vacuum.rb b/app/lib/vacuum/invites_vacuum.rb index 82eddee267..3d7baba7d7 100644 --- a/app/lib/vacuum/invites_vacuum.rb +++ b/app/lib/vacuum/invites_vacuum.rb @@ -13,13 +13,13 @@ class Vacuum::InvitesVacuum private def expire_invites! - invites = Invite.where('created_at < ?', @retention_period.ago) + invites = Invite.available.where('created_at < ?', @retention_period.ago) invites = if @max_uses.present? invites.where('max_uses > ? OR max_uses IS NULL', @max_uses) else invites.where(max_uses: nil) end - invites.reorder(nil).in_batches(&:expire!) + invites.reorder(nil).find_each(&:expire!) end end diff --git a/spec/lib/vacuum/invites_vacuum_spec.rb b/spec/lib/vacuum/invites_vacuum_spec.rb index 1f4833e2b2..b6c03f8330 100644 --- a/spec/lib/vacuum/invites_vacuum_spec.rb +++ b/spec/lib/vacuum/invites_vacuum_spec.rb @@ -8,11 +8,13 @@ RSpec.describe Vacuum::InvitesVacuum do let(:retention_period) { 7.days } let(:retention_max_uses) { 10 } + let(:user) { Fabricate(:user) } + describe '#perform' do - invite_unlimited = Fabricate(:invite, max_uses: nil, expires_at: nil) - invite_huge_max_uses = Fabricate(:invite, max_uses: 100, expires_at: nil) - invite_small_max_uses = Fabricate(:invite, max_uses: 2, expires_at: nil) - invite_will_expires = Fabricate(:invite, max_uses: nil, created_at: 1.hour.ago, expires_at: 1.hour.from_now) + let!(:invite_unlimited) { Fabricate(:invite, user: user, max_uses: nil, created_at: 10.days.ago, expires_at: nil) } + let!(:invite_huge_max_uses) { Fabricate(:invite, max_uses: 100, created_at: 10.days.ago, expires_at: nil) } + let!(:invite_small_max_uses) { Fabricate(:invite, max_uses: 2, created_at: 10.days.ago, expires_at: nil) } + let!(:invite_will_expires_later) { Fabricate(:invite, max_uses: nil, created_at: 1.hour.ago, expires_at: 1.hour.from_now) } before do subject.perform @@ -31,7 +33,7 @@ RSpec.describe Vacuum::InvitesVacuum do end it 'expires invitation link that will expire' do - expect(invite_will_expires.reload.expired?).to be true + expect(invite_will_expires_later.reload.expired?).to be false end end end