From 24155bc009fd8296c3f17e3d76e54d267b458903 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 15 Jun 2020 13:20:48 +0200 Subject: [PATCH] ability to lock settings from the env vars --- .../javascripts/directives/settings/boolean-setting.js.erb | 5 +++++ .../javascripts/directives/settings/number-setting.js.erb | 5 +++++ .../directives/settings/select-multiple-setting.js.erb | 5 +++++ .../javascripts/directives/settings/select-setting.js.erb | 5 +++++ .../javascripts/directives/settings/text-setting.js.erb | 5 +++++ app/controllers/api/settings_controller.rb | 6 ++++++ app/models/setting.rb | 2 ++ app/services/setting_service.rb | 6 ++++++ config/locales/app.admin.en.yml | 1 + config/locales/app.admin.fr.yml | 1 + config/secrets.yml | 4 ++++ doc/environment.md | 7 +++++++ 12 files changed, 52 insertions(+) diff --git a/app/assets/javascripts/directives/settings/boolean-setting.js.erb b/app/assets/javascripts/directives/settings/boolean-setting.js.erb index 661116386..942d9d8ab 100644 --- a/app/assets/javascripts/directives/settings/boolean-setting.js.erb +++ b/app/assets/javascripts/directives/settings/boolean-setting.js.erb @@ -66,6 +66,11 @@ Application.Directives.directive('booleanSetting', ['Setting', 'growl', '_t', function (error) { if (error.status === 304) return; + if (error.status === 423) { + growl.error(_t('app.admin.settings.error_SETTING_locked', { SETTING: _t(`app.admin.settings.${setting.name}`) })); + return; + } + growl.error(_t('app.admin.settings.an_error_occurred_saving_the_setting')); console.log(error); } diff --git a/app/assets/javascripts/directives/settings/number-setting.js.erb b/app/assets/javascripts/directives/settings/number-setting.js.erb index 32e69f793..0c5940bce 100644 --- a/app/assets/javascripts/directives/settings/number-setting.js.erb +++ b/app/assets/javascripts/directives/settings/number-setting.js.erb @@ -42,6 +42,11 @@ Application.Directives.directive('numberSetting', ['Setting', 'growl', '_t', function (error) { if (error.status === 304) return; + if (error.status === 423) { + growl.error(_t('app.admin.settings.error_SETTING_locked', { SETTING: _t(`app.admin.settings.${setting.name}`) })); + return; + } + growl.error(_t('app.admin.settings.an_error_occurred_saving_the_setting')); console.log(error); } diff --git a/app/assets/javascripts/directives/settings/select-multiple-setting.js.erb b/app/assets/javascripts/directives/settings/select-multiple-setting.js.erb index f7b40fd1c..b28b0a89f 100644 --- a/app/assets/javascripts/directives/settings/select-multiple-setting.js.erb +++ b/app/assets/javascripts/directives/settings/select-multiple-setting.js.erb @@ -87,6 +87,11 @@ Application.Directives.directive('selectMultipleSetting', ['Setting', 'growl', ' function (error) { if (error.status === 304) return; + if (error.status === 423) { + growl.error(_t('app.admin.settings.error_SETTING_locked', { SETTING: _t(`app.admin.settings.${setting.name}`) })); + return; + } + growl.error(_t('app.admin.settings.an_error_occurred_saving_the_setting')); console.log(error); } diff --git a/app/assets/javascripts/directives/settings/select-setting.js.erb b/app/assets/javascripts/directives/settings/select-setting.js.erb index 9937ad007..2c0d2ba91 100644 --- a/app/assets/javascripts/directives/settings/select-setting.js.erb +++ b/app/assets/javascripts/directives/settings/select-setting.js.erb @@ -39,6 +39,11 @@ Application.Directives.directive('selectSetting', ['Setting', 'growl', '_t', function (error) { if (error.status === 304) return; + if (error.status === 423) { + growl.error(_t('app.admin.settings.error_SETTING_locked', { SETTING: _t(`app.admin.settings.${setting.name}`) })); + return; + } + growl.error(_t('app.admin.settings.an_error_occurred_saving_the_setting')); console.log(error); } diff --git a/app/assets/javascripts/directives/settings/text-setting.js.erb b/app/assets/javascripts/directives/settings/text-setting.js.erb index bd9dc9215..2631863f9 100644 --- a/app/assets/javascripts/directives/settings/text-setting.js.erb +++ b/app/assets/javascripts/directives/settings/text-setting.js.erb @@ -50,6 +50,11 @@ Application.Directives.directive('textSetting', ['Setting', 'growl', '_t', function (error) { if (error.status === 304) return; + if (error.status === 423) { + growl.error(_t('app.admin.settings.error_SETTING_locked', { SETTING: _t(`app.admin.settings.${setting.name}`) })); + return; + } + growl.error(_t('app.admin.settings.an_error_occurred_saving_the_setting')); console.log(error); } diff --git a/app/controllers/api/settings_controller.rb b/app/controllers/api/settings_controller.rb index 08e39fc1f..0ce7598f1 100644 --- a/app/controllers/api/settings_controller.rb +++ b/app/controllers/api/settings_controller.rb @@ -12,6 +12,7 @@ class API::SettingsController < API::ApiController authorize Setting @setting = Setting.find_or_initialize_by(name: params[:name]) render status: :not_modified and return if setting_params[:value] == @setting.value + render status: :locked, json: { error: 'locked setting' } and return unless SettingService.before_update(@setting) if @setting.save && @setting.history_values.create(value: setting_params[:value], invoicing_profile: current_user.invoicing_profile) SettingService.after_update(@setting) @@ -29,6 +30,8 @@ class API::SettingsController < API::ApiController next if !setting[:name] || !setting[:value] db_setting = Setting.find_or_initialize_by(name: setting[:name]) + next unless SettingService.before_update(db_setting) + db_setting.save && db_setting.history_values.create(value: setting[:value], invoicing_profile: current_user.invoicing_profile) SettingService.after_update(db_setting) @settings.push db_setting @@ -52,12 +55,15 @@ class API::SettingsController < API::ApiController authorize Setting setting = Setting.find_or_create_by(name: params[:name]) + render status: :locked, json: { error: 'locked setting' } and return unless SettingService.before_update(setting) + first_val = setting.history_values.order(created_at: :asc).limit(1).first new_val = HistoryValue.create!( setting_id: setting.id, value: first_val.value, invoicing_profile_id: current_user.invoicing_profile.id ) + SettingService.after_update(setting) render json: new_val, status: :ok end diff --git a/app/models/setting.rb b/app/models/setting.rb index 4a8e10c95..4496fc4ff 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -6,6 +6,8 @@ # after_update callback is handled by SettingService class Setting < ApplicationRecord has_many :history_values + # The following list contains all the settings that can be customized from the Fab-manager's UI. + # A few of them that are system settings, that should not be updated manually (uuid, origin). validates :name, inclusion: { in: %w[about_title about_body diff --git a/app/services/setting_service.rb b/app/services/setting_service.rb index 7dc653aa6..6a8e1c3d0 100644 --- a/app/services/setting_service.rb +++ b/app/services/setting_service.rb @@ -4,6 +4,12 @@ # Due to the way the controller updates the settings, we cannot safely use ActiveRecord's callbacks (eg. after_update, after_commit...) # so this service provides a wrapper around these operations. class SettingService + def self.before_update(setting) + return false if Rails.application.secrets.locked_settings.include? setting.name + + true + end + def self.after_update(setting) # update the stylesheet Stylesheet.theme&.rebuild! if %w[main_color secondary_color].include? setting.name diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 10ab2ebfd..95527d571 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -1067,6 +1067,7 @@ en: advanced: "Advanced settings" customize_home_page_css: "Customise the stylesheet of the home page" home_css_notice_html: "You can customize the stylesheet which will apply to the home page, using the SASS syntax. These styles will be automatically subordinated to the .home-page selector to prevent any risk of breaking the application. Meanwhile please be careful, any changes in the home page editor at the top of the page may broke your styles, always refer to the HTML code." + error_SETTING_locked: "Unable to update the setting: {SETTING} is locked. Please contact your system administrator." an_error_occurred_saving_the_setting: "An error occurred while saving the setting. Please try again later." book_overlapping_slots_info: "Allow / prevent the reservation of overlapping slots" allow_booking: "Allow booking" diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 4356f0e0d..2a91724ab 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -1067,6 +1067,7 @@ fr: advanced: "Paramètres avancés" customize_home_page_css: "Personnaliser la feuille de style de la page d'accueil" home_css_notice_html: "Vous pouvez personnaliser la feuille de style qui s'appliquera à la page d'accueil en utilisant la syntaxe SASS. Ces styles seront automatiquement subordonnées au sélecteur .home-page pour prévenir tout risque de casse de l'application. Attention toutefois, les modifications de la page d'accueil dans l'éditeur en haut de page peuvent rendre caduque vos styles, référez vous toujours au code HTML." + error_SETTING_locked: "Impossible de mettre à jour le paramètre : {SETTING} est verrouillé. Veuillez contacter votre administrateur système." an_error_occurred_saving_the_setting: "Une erreur est survenue pendant l'enregistrement du paramètre. Veuillez réessayer plus tard." book_overlapping_slots_info: "Autoriser / empêcher la réservation de créneaux qui se chevauchent" allow_booking: "Autoriser la réservation" diff --git a/config/secrets.yml b/config/secrets.yml index 5ee02c689..834b203ac 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -36,6 +36,7 @@ development: disk_space_mb_alert: <%= ENV["DISK_SPACE_MB_ALERT"] %> superadmin_email: <%= ENV["SUPERADMIN_EMAIL"] %> allow_insecure_http: <%= ENV.fetch("ALLOW_INSECURE_HTTP", false) %> + locked_settings: <%= ENV.fetch("LOCKED_SETTINGS", 'uuid,origin').split(/,/) %> test: secret_key_base: 83daf5e7b80d990f037407bab78dff9904aaf3c195a50f84fa8695a22287e707dfbd9524b403b1dcf116ae1d8c06844c3d7ed942564e5b46be6ae3ead93a9d30 @@ -63,6 +64,7 @@ test: disk_space_mb_alert: <%= ENV["DISK_SPACE_MB_ALERT"] %> superadmin_email: <%= ENV["SUPERADMIN_EMAIL"] %> allow_insecure_http: <%= ENV.fetch("ALLOW_INSECURE_HTTP", false) %> + locked_settings: <%= ENV.fetch("LOCKED_SETTINGS", 'uuid,origin').split(/,/) %> staging: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> @@ -99,6 +101,7 @@ staging: superadmin_email: <%= ENV["SUPERADMIN_EMAIL"] %> enable_in_context_translation: <%= ENV["ENABLE_IN_CONTEXT_TRANSLATION"] %> allow_insecure_http: <%= ENV.fetch("ALLOW_INSECURE_HTTP", false) %> + locked_settings: <%= ENV.fetch("LOCKED_SETTINGS", 'uuid,origin').split(/,/) %> # Do not keep production secrets in the repository, # instead read values from the environment. @@ -136,3 +139,4 @@ production: disk_space_mb_alert: <%= ENV["DISK_SPACE_MB_ALERT"] %> superadmin_email: <%= ENV["SUPERADMIN_EMAIL"] %> allow_insecure_http: <%= ENV.fetch("ALLOW_INSECURE_HTTP", false) %> + locked_settings: <%= ENV.fetch("LOCKED_SETTINGS", 'uuid,origin').split(/,/) %> diff --git a/doc/environment.md b/doc/environment.md index f178d9e31..0c19298e6 100644 --- a/doc/environment.md +++ b/doc/environment.md @@ -128,6 +128,13 @@ In test and development environments, the version won't be check automatically, In production and staging environments, the session cookie won't be sent to the server unless through the HTTPS protocol. If you're using Fab-manager on a non-public network or for testing purposes, you can disable this behavior by setting this variable to `true`. Please, ensure you know what you're doing, as this can lead to serious security issues. + + + LOCKED_SETTINGS + +A comma separated list of settings that cannot be changed from the UI. +Please refer to https://github.com/sleede/fab-manager/blob/master/app/models/setting.rb for a list of possible values. +Only the system administrator can change them, with the command: `ENV=value rails fablab:setup:env_to_db` ## Internationalization setting.