mirror of
https://github.com/discourse/discourse.git
synced 2026-03-04 01:15:08 +08:00
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.
96 lines
3 KiB
Ruby
96 lines
3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class PresenceController < ApplicationController
|
|
skip_before_action :check_xhr, :redirect_to_profile_if_required
|
|
before_action :ensure_logged_in, only: [:update]
|
|
before_action :skip_persist_session
|
|
|
|
MAX_CHANNELS_PER_REQUEST = 50
|
|
|
|
def get
|
|
names = params.require(:channels)
|
|
if !(names.is_a?(Array) && names.all? { |n| n.is_a? String })
|
|
raise Discourse::InvalidParameters.new(:channels)
|
|
end
|
|
|
|
names.uniq!
|
|
|
|
if names.length > MAX_CHANNELS_PER_REQUEST
|
|
raise Discourse::InvalidParameters.new("Too many channels")
|
|
end
|
|
|
|
user_group_ids =
|
|
if current_user
|
|
GroupUser.where(user_id: current_user.id).pluck("group_id")
|
|
else
|
|
[]
|
|
end
|
|
|
|
result = {}
|
|
names.each do |name|
|
|
channel = PresenceChannel.new(name)
|
|
if channel.can_view?(user_id: current_user&.id, group_ids: user_group_ids)
|
|
result[name] = PresenceChannelStateSerializer.new(channel.state, root: nil)
|
|
else
|
|
result[name] = nil
|
|
end
|
|
rescue PresenceChannel::NotFound
|
|
result[name] = nil
|
|
end
|
|
|
|
render json: result
|
|
end
|
|
|
|
def update
|
|
client_id = params[:client_id]
|
|
if !client_id.is_a?(String) || client_id.blank?
|
|
raise Discourse::InvalidParameters.new(:client_id)
|
|
end
|
|
|
|
# JS client is designed to throttle to one request per second
|
|
# When no changes are being made, it makes one request every 30 seconds
|
|
RateLimiter.new(nil, "update-presence-#{current_user.id}", 20, 10.seconds).performed!
|
|
|
|
present_channels = params[:present_channels]
|
|
if present_channels &&
|
|
!(present_channels.is_a?(Array) && present_channels.all? { |c| c.is_a? String })
|
|
raise Discourse::InvalidParameters.new(:present_channels)
|
|
end
|
|
|
|
leave_channels = params[:leave_channels]
|
|
if leave_channels &&
|
|
!(leave_channels.is_a?(Array) && leave_channels.all? { |c| c.is_a? String })
|
|
raise Discourse::InvalidParameters.new(:leave_channels)
|
|
end
|
|
|
|
if present_channels && present_channels.length > MAX_CHANNELS_PER_REQUEST
|
|
raise Discourse::InvalidParameters.new("Too many present_channels")
|
|
end
|
|
|
|
response = {}
|
|
|
|
present_channels&.each do |name|
|
|
PresenceChannel.new(name).present(user_id: current_user&.id, client_id: params[:client_id])
|
|
response[name] = true
|
|
rescue PresenceChannel::NotFound, PresenceChannel::InvalidAccess
|
|
response[name] = false
|
|
end
|
|
|
|
leave_channels&.each do |name|
|
|
PresenceChannel.new(name).leave(user_id: current_user&.id, client_id: params[:client_id])
|
|
rescue PresenceChannel::NotFound
|
|
# Do nothing. Don't reveal that this channel doesn't exist
|
|
end
|
|
|
|
render json: response
|
|
end
|
|
|
|
private
|
|
|
|
def skip_persist_session
|
|
# Presence endpoints are often called asynchronously at the same time as other request,
|
|
# and never need to modify the session. Skipping ensures that an unneeded cookie rotation
|
|
# doesn't race against another request and cause issues.
|
|
session.options[:skip] = true
|
|
end
|
|
end
|