2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2025-10-03 17:21:20 +08:00
discourse/lib/read_only_mixin.rb
Régis Hanol bccf4e0b53
FIX: improve "read only" modes (#33521)
The reasons for these changes is https://meta.discourse.org/t/-/89605
broke and admins were not able to log back in if they had previously
enabled the "read only" mode.

Thus ensued a deep dive into how all the "read only" modes worked, which
was made difficult due to the lack of tests.

The "cornerstone" of this PR is the `read_only_mixin.rb` file which was
improved to be able to differentiate between the "readonly" mode and the
"staff writes only" mode.

I then made use of the `allow_in_readonly_mode` and
`allow_in_staff_writes_only_mode` method to **explicitely** list all the
actions that should work in those modes.

I also added the "readonly" mixin to the `WebhooksController` since it
doesn't inherit from the `ApplicationController`.

I improved the security of the `/u/admin-login` endpoint by always
sending the same message no matter if we found or not an admin account
with the provided email address.

I added two system specs:

1. for ensuring that admins can log in via /u/admin-lgoin and then
clicking the link in the email they received while the site is in
readonly mode.
2. for ensuring the "staff writes only mode" is _actually_ tested by
ensuring a moderator can log in and create a topic while the site is in
that mode.

Plenty of specs were updated to ensure 100% converage of the various
"read only" modes.
2025-07-10 09:08:00 +02:00

67 lines
1.7 KiB
Ruby

# frozen_string_literal: true
module ReadOnlyMixin
module ClassMethods
def actions_allowed_in_readonly_mode
@actions_allowed_in_readonly_mode ||= []
end
def actions_allowed_in_staff_writes_only_mode
@actions_allowed_in_staff_writes_only_mode ||= []
end
def allow_in_readonly_mode(*actions)
actions_allowed_in_readonly_mode.concat(actions.map(&:to_sym))
end
def allow_in_staff_writes_only_mode(*actions)
actions_allowed_in_staff_writes_only_mode.concat(actions.map(&:to_sym))
end
def allowed_in_readonly_mode?(action)
actions_allowed_in_readonly_mode.include?(action.to_sym)
end
def allowed_in_staff_writes_only_mode?(action)
actions_allowed_in_staff_writes_only_mode.include?(action.to_sym)
end
end
def check_readonly_mode
if Discourse.readonly_mode?
@readonly_mode = true
@staff_writes_only_mode = false
elsif Discourse.staff_writes_only_mode?
@readonly_mode = true
@staff_writes_only_mode = true
else
@readonly_mode = false
@staff_writes_only_mode = false
end
end
def add_readonly_header
response.headers["Discourse-Readonly"] = "true" if @readonly_mode
end
def allowed_in_readonly_mode?
self.class.allowed_in_readonly_mode?(action_name)
end
def allowed_in_staff_writes_only_mode?
self.class.allowed_in_staff_writes_only_mode?(action_name)
end
def block_if_readonly_mode
return if request.get? || request.head?
if @staff_writes_only_mode && (allowed_in_staff_writes_only_mode? || current_user&.staff?)
return
end
return if !@readonly_mode || allowed_in_readonly_mode?
raise Discourse::ReadOnly
end
def self.included(base)
base.extend(ClassMethods)
end
end