Add validations to admin settings (#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
This commit is contained in:
Eugen Rochko 2019-03-23 14:07:04 +01:00 committed by GitHub
parent b4c64dcccc
commit c21bdc81d3
8 changed files with 140 additions and 110 deletions

View file

@ -2,84 +2,29 @@
module Admin module Admin
class SettingsController < BaseController class SettingsController < BaseController
ADMIN_SETTINGS = %w(
site_contact_username
site_contact_email
site_title
site_short_description
site_description
site_extended_description
site_terms
registrations_mode
closed_registrations_message
open_deletion
timeline_preview
show_staff_badge
bootstrap_timeline_accounts
theme
thumbnail
hero
mascot
min_invite_role
activity_api_enabled
peers_api_enabled
show_known_fediverse_at_about_page
preview_sensitive_media
custom_css
profile_directory
).freeze
BOOLEAN_SETTINGS = %w(
open_deletion
timeline_preview
show_staff_badge
activity_api_enabled
peers_api_enabled
show_known_fediverse_at_about_page
preview_sensitive_media
profile_directory
).freeze
UPLOAD_SETTINGS = %w(
thumbnail
hero
mascot
).freeze
def edit def edit
authorize :settings, :show? authorize :settings, :show?
@admin_settings = Form::AdminSettings.new @admin_settings = Form::AdminSettings.new
end end
def update def update
authorize :settings, :update? authorize :settings, :update?
settings_params.each do |key, value| @admin_settings = Form::AdminSettings.new(settings_params)
if UPLOAD_SETTINGS.include?(key)
upload = SiteUpload.where(var: key).first_or_initialize(var: key)
upload.update(file: value)
else
setting = Setting.where(var: key).first_or_initialize(var: key)
setting.update(value: value_for_update(key, value))
end
end
flash[:notice] = I18n.t('generic.changes_saved_msg') if @admin_settings.save
redirect_to edit_admin_settings_path flash[:notice] = I18n.t('generic.changes_saved_msg')
redirect_to edit_admin_settings_path
else
render :edit
end
end end
private private
def settings_params def settings_params
params.require(:form_admin_settings).permit(ADMIN_SETTINGS) params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
end
def value_for_update(key, value)
if BOOLEAN_SETTINGS.include?(key)
value == '1'
else
value
end
end end
end end
end end

View file

@ -3,49 +3,90 @@
class Form::AdminSettings class Form::AdminSettings
include ActiveModel::Model include ActiveModel::Model
delegate( KEYS = %i(
:site_contact_username, site_contact_username
:site_contact_username=, site_contact_email
:site_contact_email, site_title
:site_contact_email=, site_short_description
:site_title, site_description
:site_title=, site_extended_description
:site_short_description, site_terms
:site_short_description=, registrations_mode
:site_description, closed_registrations_message
:site_description=, open_deletion
:site_extended_description, timeline_preview
:site_extended_description=, show_staff_badge
:site_terms, bootstrap_timeline_accounts
:site_terms=, theme
:registrations_mode, min_invite_role
:registrations_mode=, activity_api_enabled
:closed_registrations_message, peers_api_enabled
:closed_registrations_message=, show_known_fediverse_at_about_page
:open_deletion, preview_sensitive_media
:open_deletion=, custom_css
:timeline_preview, profile_directory
:timeline_preview=, ).freeze
:show_staff_badge,
:show_staff_badge=, BOOLEAN_KEYS = %i(
:bootstrap_timeline_accounts, open_deletion
:bootstrap_timeline_accounts=, timeline_preview
:theme, show_staff_badge
:theme=, activity_api_enabled
:min_invite_role, peers_api_enabled
:min_invite_role=, show_known_fediverse_at_about_page
:activity_api_enabled, preview_sensitive_media
:activity_api_enabled=, profile_directory
:peers_api_enabled, ).freeze
:peers_api_enabled=,
:show_known_fediverse_at_about_page, UPLOAD_KEYS = %i(
:show_known_fediverse_at_about_page=, thumbnail
:preview_sensitive_media, hero
:preview_sensitive_media=, mascot
:custom_css, ).freeze
:custom_css=,
:profile_directory, attr_accessor(*KEYS)
:profile_directory=,
to: Setting validates :site_short_description, :site_description, :site_extended_description, :site_terms, :closed_registrations_message, html: true
) validates :registrations_mode, inclusion: { in: %w(open approved none) }
validates :min_invite_role, inclusion: { in: %w(disabled user moderator admin) }
validates :site_contact_email, :site_contact_username, presence: true
validates :site_contact_username, existing_username: true
validates :bootstrap_timeline_accounts, existing_username: { multiple: true }
def initialize(_attributes = {})
super
initialize_attributes
end
def save
return false unless valid?
KEYS.each do |key|
value = instance_variable_get("@#{key}")
if UPLOAD_KEYS.include?(key)
upload = SiteUpload.where(var: key).first_or_initialize(var: key)
upload.update(file: value)
else
setting = Setting.where(var: key).first_or_initialize(var: key)
setting.update(value: typecast_value(key, value))
end
end
end
private
def initialize_attributes
KEYS.each do |key|
instance_variable_set("@#{key}", Setting.public_send(key)) if instance_variable_get("@#{key}").nil?
end
end
def typecast_value(key, value)
if BOOLEAN_KEYS.include?(key)
value == '1'
else
value
end
end
end end

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
class ExistingUsernameValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if value.blank?
if options[:multiple]
missing_usernames = value.split(',').map { |username| username unless Account.find_local(username) }.compact
record.errors.add(attribute, I18n.t('existing_username_validator.not_found_multiple', usernames: missing_usernames.join(', '))) if missing_usernames.any?
else
record.errors.add(attribute, I18n.t('existing_username_validator.not_found')) unless Account.find_local(value)
end
end
private
def valid_html?(str)
Nokogiri::HTML.fragment(str).to_s == str
end
end

View file

@ -0,0 +1,14 @@
# frozen_string_literal: true
class HtmlValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if value.blank?
record.errors.add(attribute, I18n.t('html_validator.invalid_markup')) unless valid_html?(value)
end
private
def valid_html?(str)
Nokogiri::HTML.fragment(str).to_s == str
end
end

View file

@ -2,6 +2,7 @@
= t('admin.settings.title') = t('admin.settings.title')
= simple_form_for @admin_settings, url: admin_settings_path, html: { method: :patch } do |f| = simple_form_for @admin_settings, url: admin_settings_path, html: { method: :patch } do |f|
= render 'shared/error_messages', object: @admin_settings
.fields-group .fields-group
= f.input :site_title, wrapper: :with_label, label: t('admin.settings.site_title') = f.input :site_title, wrapper: :with_label, label: t('admin.settings.site_title')

View file

@ -586,6 +586,9 @@ en:
content: We're sorry, but something went wrong on our end. content: We're sorry, but something went wrong on our end.
title: This page is not correct title: This page is not correct
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. 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.
existing_username_validator:
not_found: could not find a local user with that username
not_found_multiple: could not find %{usernames}
exports: exports:
archive_takeout: archive_takeout:
date: Date date: Date
@ -633,6 +636,8 @@ en:
validation_errors: validation_errors:
one: Something isn't quite right yet! Please review the error below one: Something isn't quite right yet! Please review the error below
other: Something isn't quite right yet! Please review %{count} errors below other: Something isn't quite right yet! Please review %{count} errors below
html_validator:
invalid_markup: contains invalid HTML markup
identity_proofs: identity_proofs:
active: Active active: Active
authorize: Yes, authorize authorize: Yes, authorize

View file

@ -37,7 +37,7 @@ SimpleNavigation::Configuration.run do |navigation|
primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_dashboard_url, if: proc { current_user.staff? } do |admin| primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_dashboard_url, if: proc { current_user.staff? } do |admin|
admin.item :dashboard, safe_join([fa_icon('tachometer fw'), t('admin.dashboard.title')]), admin_dashboard_url admin.item :dashboard, safe_join([fa_icon('tachometer fw'), t('admin.dashboard.title')]), admin_dashboard_url
admin.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? } 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}
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} 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}
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} 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}
admin.item :subscriptions, safe_join([fa_icon('paper-plane-o fw'), t('admin.subscriptions.title')]), admin_subscriptions_url, if: -> { current_user.admin? } admin.item :subscriptions, safe_join([fa_icon('paper-plane-o fw'), t('admin.subscriptions.title')]), admin_subscriptions_url, if: -> { current_user.admin? }

View file

@ -19,6 +19,10 @@ RSpec.describe Admin::SettingsController, type: :controller do
end end
describe 'PUT #update' do describe 'PUT #update' do
before do
allow_any_instance_of(Form::AdminSettings).to receive(:valid?).and_return(true)
end
describe 'for a record that doesnt exist' do describe 'for a record that doesnt exist' do
around do |example| around do |example|
before = Setting.site_extended_description before = Setting.site_extended_description