Skip to content

Commit c6e9e93

Browse files
Gargronhiyuki2578
authored andcommitted
Add validations to admin settings (mastodon#10348)
* Add validations to admin settings - Validate correct HTML markup - Validate presence of contact username & e-mail - Validate that all usernames are valid - Validate that enums have expected values * Fix code style issue * Fix tests
1 parent 9ba6a67 commit c6e9e93

8 files changed

Lines changed: 140 additions & 110 deletions

File tree

app/controllers/admin/settings_controller.rb

Lines changed: 9 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,84 +2,29 @@
22

33
module Admin
44
class SettingsController < BaseController
5-
ADMIN_SETTINGS = %w(
6-
site_contact_username
7-
site_contact_email
8-
site_title
9-
site_short_description
10-
site_description
11-
site_extended_description
12-
site_terms
13-
registrations_mode
14-
closed_registrations_message
15-
open_deletion
16-
timeline_preview
17-
show_staff_badge
18-
bootstrap_timeline_accounts
19-
theme
20-
thumbnail
21-
hero
22-
mascot
23-
min_invite_role
24-
activity_api_enabled
25-
peers_api_enabled
26-
show_known_fediverse_at_about_page
27-
preview_sensitive_media
28-
custom_css
29-
profile_directory
30-
).freeze
31-
32-
BOOLEAN_SETTINGS = %w(
33-
open_deletion
34-
timeline_preview
35-
show_staff_badge
36-
activity_api_enabled
37-
peers_api_enabled
38-
show_known_fediverse_at_about_page
39-
preview_sensitive_media
40-
profile_directory
41-
).freeze
42-
43-
UPLOAD_SETTINGS = %w(
44-
thumbnail
45-
hero
46-
mascot
47-
).freeze
48-
495
def edit
506
authorize :settings, :show?
7+
518
@admin_settings = Form::AdminSettings.new
529
end
5310

5411
def update
5512
authorize :settings, :update?
5613

57-
settings_params.each do |key, value|
58-
if UPLOAD_SETTINGS.include?(key)
59-
upload = SiteUpload.where(var: key).first_or_initialize(var: key)
60-
upload.update(file: value)
61-
else
62-
setting = Setting.where(var: key).first_or_initialize(var: key)
63-
setting.update(value: value_for_update(key, value))
64-
end
65-
end
14+
@admin_settings = Form::AdminSettings.new(settings_params)
6615

67-
flash[:notice] = I18n.t('generic.changes_saved_msg')
68-
redirect_to edit_admin_settings_path
16+
if @admin_settings.save
17+
flash[:notice] = I18n.t('generic.changes_saved_msg')
18+
redirect_to edit_admin_settings_path
19+
else
20+
render :edit
21+
end
6922
end
7023

7124
private
7225

7326
def settings_params
74-
params.require(:form_admin_settings).permit(ADMIN_SETTINGS)
75-
end
76-
77-
def value_for_update(key, value)
78-
if BOOLEAN_SETTINGS.include?(key)
79-
value == '1'
80-
else
81-
value
82-
end
27+
params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
8328
end
8429
end
8530
end

app/models/form/admin_settings.rb

Lines changed: 86 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,49 +3,90 @@
33
class Form::AdminSettings
44
include ActiveModel::Model
55

6-
delegate(
7-
:site_contact_username,
8-
:site_contact_username=,
9-
:site_contact_email,
10-
:site_contact_email=,
11-
:site_title,
12-
:site_title=,
13-
:site_short_description,
14-
:site_short_description=,
15-
:site_description,
16-
:site_description=,
17-
:site_extended_description,
18-
:site_extended_description=,
19-
:site_terms,
20-
:site_terms=,
21-
:registrations_mode,
22-
:registrations_mode=,
23-
:closed_registrations_message,
24-
:closed_registrations_message=,
25-
:open_deletion,
26-
:open_deletion=,
27-
:timeline_preview,
28-
:timeline_preview=,
29-
:show_staff_badge,
30-
:show_staff_badge=,
31-
:bootstrap_timeline_accounts,
32-
:bootstrap_timeline_accounts=,
33-
:theme,
34-
:theme=,
35-
:min_invite_role,
36-
:min_invite_role=,
37-
:activity_api_enabled,
38-
:activity_api_enabled=,
39-
:peers_api_enabled,
40-
:peers_api_enabled=,
41-
:show_known_fediverse_at_about_page,
42-
:show_known_fediverse_at_about_page=,
43-
:preview_sensitive_media,
44-
:preview_sensitive_media=,
45-
:custom_css,
46-
:custom_css=,
47-
:profile_directory,
48-
:profile_directory=,
49-
to: Setting
50-
)
6+
KEYS = %i(
7+
site_contact_username
8+
site_contact_email
9+
site_title
10+
site_short_description
11+
site_description
12+
site_extended_description
13+
site_terms
14+
registrations_mode
15+
closed_registrations_message
16+
open_deletion
17+
timeline_preview
18+
show_staff_badge
19+
bootstrap_timeline_accounts
20+
theme
21+
min_invite_role
22+
activity_api_enabled
23+
peers_api_enabled
24+
show_known_fediverse_at_about_page
25+
preview_sensitive_media
26+
custom_css
27+
profile_directory
28+
).freeze
29+
30+
BOOLEAN_KEYS = %i(
31+
open_deletion
32+
timeline_preview
33+
show_staff_badge
34+
activity_api_enabled
35+
peers_api_enabled
36+
show_known_fediverse_at_about_page
37+
preview_sensitive_media
38+
profile_directory
39+
).freeze
40+
41+
UPLOAD_KEYS = %i(
42+
thumbnail
43+
hero
44+
mascot
45+
).freeze
46+
47+
attr_accessor(*KEYS)
48+
49+
validates :site_short_description, :site_description, :site_extended_description, :site_terms, :closed_registrations_message, html: true
50+
validates :registrations_mode, inclusion: { in: %w(open approved none) }
51+
validates :min_invite_role, inclusion: { in: %w(disabled user moderator admin) }
52+
validates :site_contact_email, :site_contact_username, presence: true
53+
validates :site_contact_username, existing_username: true
54+
validates :bootstrap_timeline_accounts, existing_username: { multiple: true }
55+
56+
def initialize(_attributes = {})
57+
super
58+
initialize_attributes
59+
end
60+
61+
def save
62+
return false unless valid?
63+
64+
KEYS.each do |key|
65+
value = instance_variable_get("@#{key}")
66+
67+
if UPLOAD_KEYS.include?(key)
68+
upload = SiteUpload.where(var: key).first_or_initialize(var: key)
69+
upload.update(file: value)
70+
else
71+
setting = Setting.where(var: key).first_or_initialize(var: key)
72+
setting.update(value: typecast_value(key, value))
73+
end
74+
end
75+
end
76+
77+
private
78+
79+
def initialize_attributes
80+
KEYS.each do |key|
81+
instance_variable_set("@#{key}", Setting.public_send(key)) if instance_variable_get("@#{key}").nil?
82+
end
83+
end
84+
85+
def typecast_value(key, value)
86+
if BOOLEAN_KEYS.include?(key)
87+
value == '1'
88+
else
89+
value
90+
end
91+
end
5192
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# frozen_string_literal: true
2+
3+
class ExistingUsernameValidator < ActiveModel::EachValidator
4+
def validate_each(record, attribute, value)
5+
return if value.blank?
6+
7+
if options[:multiple]
8+
missing_usernames = value.split(',').map { |username| username unless Account.find_local(username) }.compact
9+
record.errors.add(attribute, I18n.t('existing_username_validator.not_found_multiple', usernames: missing_usernames.join(', '))) if missing_usernames.any?
10+
else
11+
record.errors.add(attribute, I18n.t('existing_username_validator.not_found')) unless Account.find_local(value)
12+
end
13+
end
14+
15+
private
16+
17+
def valid_html?(str)
18+
Nokogiri::HTML.fragment(str).to_s == str
19+
end
20+
end

app/validators/html_validator.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# frozen_string_literal: true
2+
3+
class HtmlValidator < ActiveModel::EachValidator
4+
def validate_each(record, attribute, value)
5+
return if value.blank?
6+
record.errors.add(attribute, I18n.t('html_validator.invalid_markup')) unless valid_html?(value)
7+
end
8+
9+
private
10+
11+
def valid_html?(str)
12+
Nokogiri::HTML.fragment(str).to_s == str
13+
end
14+
end

app/views/admin/settings/edit.html.haml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
= t('admin.settings.title')
33

44
= simple_form_for @admin_settings, url: admin_settings_path, html: { method: :patch } do |f|
5+
= render 'shared/error_messages', object: @admin_settings
56

67
.fields-group
78
= f.input :site_title, wrapper: :with_label, label: t('admin.settings.site_title')

config/locales/en.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,9 @@ en:
586586
content: We're sorry, but something went wrong on our end.
587587
title: This page is not correct
588588
noscript_html: To use the Mastodon web application, please enable JavaScript. Alternatively, try one of the <a href="%{apps_path}">native apps</a> for Mastodon for your platform.
589+
existing_username_validator:
590+
not_found: could not find a local user with that username
591+
not_found_multiple: could not find %{usernames}
589592
exports:
590593
archive_takeout:
591594
date: Date
@@ -633,6 +636,8 @@ en:
633636
validation_errors:
634637
one: Something isn't quite right yet! Please review the error below
635638
other: Something isn't quite right yet! Please review %{count} errors below
639+
html_validator:
640+
invalid_markup: contains invalid HTML markup
636641
identity_proofs:
637642
active: Active
638643
authorize: Yes, authorize

config/navigation.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737

3838
primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_dashboard_url, if: proc { current_user.staff? } do |admin|
3939
admin.item :dashboard, safe_join([fa_icon('tachometer fw'), t('admin.dashboard.title')]), admin_dashboard_url
40-
admin.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? }
40+
admin.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? }, highlights_on: %r{/admin/settings}
4141
admin.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_url, highlights_on: %r{/admin/custom_emojis}
4242
admin.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_url, if: -> { current_user.admin? }, highlights_on: %r{/admin/relays}
4343
admin.item :subscriptions, safe_join([fa_icon('paper-plane-o fw'), t('admin.subscriptions.title')]), admin_subscriptions_url, if: -> { current_user.admin? }

spec/controllers/admin/settings_controller_spec.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
end
2020

2121
describe 'PUT #update' do
22+
before do
23+
allow_any_instance_of(Form::AdminSettings).to receive(:valid?).and_return(true)
24+
end
25+
2226
describe 'for a record that doesnt exist' do
2327
around do |example|
2428
before = Setting.site_extended_description

0 commit comments

Comments
 (0)