diff --git a/app/assets/javascripts/discourse/components/global-notice.js.es6 b/app/assets/javascripts/discourse/components/global-notice.js.es6 index f61798163fb..13994f28fbf 100644 --- a/app/assets/javascripts/discourse/components/global-notice.js.es6 +++ b/app/assets/javascripts/discourse/components/global-notice.js.es6 @@ -17,6 +17,14 @@ export default Ember.Component.extend(StringBuffer, { notices.push([I18n.t("emails_are_disabled"), 'alert-emails-disabled']); } + if (this.currentUser && this.currentUser.get('staff') && this.siteSettings.bootstrap_mode_enabled) { + if (this.siteSettings.bootstrap_mode_min_users > 0) { + notices.push([I18n.t("bootstrap_mode_enabled", {min_users: this.siteSettings.bootstrap_mode_min_users}), 'alert-bootstrap-mode']); + } else { + notices.push([I18n.t("bootstrap_mode_disabled"), 'alert-bootstrap-mode']); + } + } + if (!_.isEmpty(this.siteSettings.global_notice)) { notices.push([this.siteSettings.global_notice, 'alert-global-notice']); } diff --git a/app/controllers/admin/site_settings_controller.rb b/app/controllers/admin/site_settings_controller.rb index b8170147800..30ad7cd4af6 100644 --- a/app/controllers/admin/site_settings_controller.rb +++ b/app/controllers/admin/site_settings_controller.rb @@ -10,9 +10,7 @@ class Admin::SiteSettingsController < Admin::AdminController value = params[id] value.strip! if value.is_a?(String) begin - prev_value = SiteSetting.send(id) - SiteSetting.set(id, value) - StaffActionLogger.new(current_user).log_site_setting_change(id, prev_value, value) if SiteSetting.has_setting?(id) + SiteSetting.set_and_log(id, value, current_user) render nothing: true rescue Discourse::InvalidParameters => e render json: {errors: [e.message]}, status: 422 diff --git a/app/jobs/regular/enable_bootstrap_mode.rb b/app/jobs/regular/enable_bootstrap_mode.rb new file mode 100644 index 00000000000..fcbd5b2921f --- /dev/null +++ b/app/jobs/regular/enable_bootstrap_mode.rb @@ -0,0 +1,18 @@ +module Jobs + class EnableBootstrapMode < Jobs::Base + sidekiq_options queue: 'critical' + + def execute(args) + raise Discourse::InvalidParameters.new(:user_id) unless args[:user_id].present? + return if SiteSetting.bootstrap_mode_enabled + + user = User.find_by(id: args[:user_id]) + return unless user.is_singular_admin? + + # let's enable bootstrap mode settings + SiteSetting.set_and_log('default_trust_level', TrustLevel[1]) + SiteSetting.set_and_log('default_email_digest_frequency', 1440) + SiteSetting.set_and_log('bootstrap_mode_enabled', true) + end + end +end diff --git a/app/jobs/scheduled/disable_bootstrap_mode.rb b/app/jobs/scheduled/disable_bootstrap_mode.rb new file mode 100644 index 00000000000..ae1707c37f2 --- /dev/null +++ b/app/jobs/scheduled/disable_bootstrap_mode.rb @@ -0,0 +1,16 @@ +module Jobs + class DisableBootstrapMode < Jobs::Scheduled + every 1.day + + def execute(args) + return unless SiteSetting.bootstrap_mode_enabled + total_users = User.where.not(id: Discourse::SYSTEM_USER_ID).count + + if SiteSetting.bootstrap_mode_min_users == 0 || total_users > SiteSetting.bootstrap_mode_min_users + SiteSetting.set_and_log('default_trust_level', TrustLevel[0]) + SiteSetting.set_and_log('default_email_digest_frequency', 10080) + SiteSetting.set_and_log('bootstrap_mode_enabled', false) + end + end + end +end diff --git a/app/models/user.rb b/app/models/user.rb index db2e267ae0d..8d68c337b07 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -845,6 +845,10 @@ class User < ActiveRecord::Base custom_fields["master_id"].to_i > 0 end + def is_singular_admin? + User.where(admin: true).where.not(id: id).where.not(id: Discourse::SYSTEM_USER_ID).blank? + end + protected def badge_grant diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index d58b84c7c22..482b17ee354 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -147,6 +147,9 @@ en: emails_are_disabled: "All outgoing email has been globally disabled by an administrator. No email notifications of any kind will be sent." + bootstrap_mode_enabled: "To make launching your new site easier, you are in bootstrap mode. All new users will be granted trust level 1 and have daily email digest updates enabled. This will be automatically turned off when total user count exceeds %{min_users} users." + bootstrap_mode_disabled: "Bootstrap mode will be disabled in next 24 hours." + s3: regions: us_east_1: "US East (N. Virginia)" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 55aff601897..4b19733f5ae 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1272,6 +1272,8 @@ en: delete_drafts_older_than_n_days: Delete drafts older than (n) days. + bootstrap_mode_min_users: "Minimum number of users required to disable bootstrap mode (set to 0 to disable)" + vacuum_db_days: "Run VACUUM ANALYZE to reclaim DB space after migrations (set to 0 to disable)" prevent_anons_from_downloading_files: "Prevent anonymous users from downloading attachments. WARNING: this will prevent any non-image site assets posted as attachments from working." diff --git a/config/site_settings.yml b/config/site_settings.yml index bc2bb0708b6..ea6fdf53240 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -1090,6 +1090,17 @@ uncategorized: default: -1 hidden: true + bootstrap_mode_min_users: + default: 50 + client: true + min: 0 + max: 5000 + + bootstrap_mode_enabled: + default: false + client: true + hidden: true + automatically_unpin_topics: default: true client: true diff --git a/lib/auth/default_current_user_provider.rb b/lib/auth/default_current_user_provider.rb index 1e5cec773a3..0bc2bf34e76 100644 --- a/lib/auth/default_current_user_provider.rb +++ b/lib/auth/default_current_user_provider.rb @@ -68,6 +68,7 @@ class Auth::DefaultCurrentUserProvider end cookies.permanent[TOKEN_COOKIE] = { value: user.auth_token, httponly: true } make_developer_admin(user) + enable_bootstrap_mode(user) @env[CURRENT_USER_KEY] = user end @@ -81,6 +82,10 @@ class Auth::DefaultCurrentUserProvider end end + def enable_bootstrap_mode(user) + Jobs.enqueue(:enable_bootstrap_mode, user_id: user.id) if user.admin && user.last_seen_at.nil? && !SiteSetting.bootstrap_mode_enabled && user.is_singular_admin? + end + def log_off_user(session, cookies) if SiteSetting.log_out_strict && (user = current_user) user.auth_token = nil diff --git a/lib/site_setting_extension.rb b/lib/site_setting_extension.rb index 90870a46734..4395a8c9075 100644 --- a/lib/site_setting_extension.rb +++ b/lib/site_setting_extension.rb @@ -371,6 +371,12 @@ module SiteSettingExtension end end + def set_and_log(name, value, user=Discourse.system_user) + prev_value = send(name) + set(name, value) + StaffActionLogger.new(user).log_site_setting_change(name, prev_value, value) if has_setting?(name) + end + protected def clear_cache! diff --git a/spec/controllers/categories_controller_spec.rb b/spec/controllers/categories_controller_spec.rb index d9d9240b544..4850d5f36ac 100644 --- a/spec/controllers/categories_controller_spec.rb +++ b/spec/controllers/categories_controller_spec.rb @@ -64,7 +64,7 @@ describe CategoriesController do expect(category.slug).to eq("hello-cat") expect(category.color).to eq("ff0") expect(category.auto_close_hours).to eq(72) - expect(UserHistory.count).to eq(1) + expect(UserHistory.count).to eq(4) # 1 + 3 (bootstrap mode) end end end @@ -228,7 +228,7 @@ describe CategoriesController do "everyone" => CategoryGroup.permission_types[:create_post] } - expect(UserHistory.count).to eq(2) + expect(UserHistory.count).to eq(5) # 2 + 3 (bootstrap mode) end end end diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index 4a57897e771..90c3426b17d 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -77,9 +77,8 @@ describe UploadsController do end it 'correctly sets retain_hours for admins' do - Jobs.expects(:enqueue).with(:create_thumbnails, anything) - log_in :admin + Jobs.expects(:enqueue).with(:create_thumbnails, anything) message = MessageBus.track_publish do xhr :post, :create, file: logo, retain_hours: 100, type: "profile_background" diff --git a/spec/jobs/disable_bootstrap_mode_spec.rb b/spec/jobs/disable_bootstrap_mode_spec.rb new file mode 100644 index 00000000000..96f11ceac3f --- /dev/null +++ b/spec/jobs/disable_bootstrap_mode_spec.rb @@ -0,0 +1,33 @@ +require 'rails_helper' + +describe Jobs::DisableBootstrapMode do + + context '.execute' do + let(:admin) { Fabricate(:admin) } + + before do + SiteSetting.bootstrap_mode_enabled = true + end + + it 'does not execute if bootstrap mode is already disabled' do + SiteSetting.bootstrap_mode_enabled = false + StaffActionLogger.any_instance.expects(:log_site_setting_change).never + Jobs::DisableBootstrapMode.new.execute(user_id: admin.id) + end + + it 'turns off bootstrap mode if bootstrap_mode_min_users is set to 0' do + SiteSetting.bootstrap_mode_min_users = 0 + StaffActionLogger.any_instance.expects(:log_site_setting_change).times(3) + Jobs::DisableBootstrapMode.new.execute(user_id: admin.id) + end + + it 'successfully turns off bootstrap mode' do + SiteSetting.bootstrap_mode_min_users = 5 + 6.times do + Fabricate(:user) + end + StaffActionLogger.any_instance.expects(:log_site_setting_change).times(3) + Jobs::DisableBootstrapMode.new.execute(user_id: admin.id) + end + end +end diff --git a/spec/jobs/enable_bootstrap_mode_spec.rb b/spec/jobs/enable_bootstrap_mode_spec.rb new file mode 100644 index 00000000000..c0441bc5fc4 --- /dev/null +++ b/spec/jobs/enable_bootstrap_mode_spec.rb @@ -0,0 +1,33 @@ +require 'rails_helper' + +describe Jobs::EnableBootstrapMode do + + context '.execute' do + let(:admin) { Fabricate(:admin) } + + before do + SiteSetting.bootstrap_mode_enabled = false + end + + it 'raises an error when user_id is missing' do + expect { Jobs::EnableBootstrapMode.new.execute({}) }.to raise_error(Discourse::InvalidParameters) + end + + it 'does not execute if bootstrap mode is already enabled' do + SiteSetting.bootstrap_mode_enabled = true + StaffActionLogger.any_instance.expects(:log_site_setting_change).never + Jobs::EnableBootstrapMode.new.execute(user_id: admin.id) + end + + it 'does not turn on bootstrap mode if first admin already exists' do + first_admin = Fabricate(:admin) + StaffActionLogger.any_instance.expects(:log_site_setting_change).never + Jobs::EnableBootstrapMode.new.execute(user_id: admin.id) + end + + it 'successfully turns on bootstrap mode' do + StaffActionLogger.any_instance.expects(:log_site_setting_change).times(3) + Jobs::EnableBootstrapMode.new.execute(user_id: admin.id) + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 42575c62f57..92e12e9e71e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -330,6 +330,20 @@ describe User do end end + describe '.is_singular_admin?' do + it 'returns true if user is singular admin' do + admin = Fabricate(:admin) + expect(admin.is_singular_admin?).to eq(true) + end + + it 'returns false if user is not the only admin' do + admin = Fabricate(:admin) + second_admin = Fabricate(:admin) + + expect(admin.is_singular_admin?).to eq(false) + end + end + describe 'name heuristics' do it 'is able to guess a decent name from an email' do expect(User.suggest_name('sam.saffron@gmail.com')).to eq('Sam Saffron')