Rails Controllers: The Request Gateway
Every AJAX request from the Ember frontend lands in a Rails controller that inherits from ApplicationController, whose before_action/after_action chain enforces CSRF protection, readonly mode, login requirements, and locale before any subclass action runs. Subclass controllers (like NotificationsController) add their own before_actions, load ActiveRecord models scoped through a guardian authorization object, and hand the results to render_serialized/render_json_dump, which serialize models into JSON and write the response Ember's ajax() helper is waiting on.
CSRF protection: protect_from_forgery + handle_unverified_request
app/controllers/application_controller.rb · lines 22–34protect_from_forgery turns on Rails' built-in CSRF protection for every action in ApplicationController and its subclasses, requiring a valid authenticity/X-CSRF-Token on state-changing requests.
handle_unverified_request overrides Rails' default forgery-failure hook. Instead of silently continuing with a blank session, Discourse clears the current user and renders a 403 "BAD CSRF" body — unless the request authenticated via API key, which is itself a secret that makes CSRF checks redundant.
protect_from_forgery # Default Rails 3.2 lets the request through with a blank session # we are being more pedantic here and nulling session / current_user # and then raising a CSRF exception def handle_unverified_request # NOTE: API key is secret, having it invalidates the need for a CSRF token unless is_api? || is_user_api? super clear_current_user render plain: "[\"BAD CSRF\"]", status: :forbidden end end
def index user = if params[:username] && !params[:recent] user_record = User.find_by(username: params[:username].to_s) raise Discourse::NotFound if !user_record user_record else current_user end guardian.ensure_can_see_notifications!(user) if notification_types = params[:filter_by_types]&.split(",").presence notification_types.map! do |type| Notification.types[type.to_sym] || (raise Discourse::InvalidParameters.new("invalid notification type: #{type}")) end end
app/controllers/notifications_controller.rb · lines 10–27def serialize_data(obj, serializer, opts = nil) # If it's an array, apply the serializer as an each_serializer to the elements serializer_opts = { scope: guardian }.merge!(opts || {}) if obj.respond_to?(:to_ary) serializer_opts[:each_serializer] = serializer ActiveModel::ArraySerializer.new(obj.to_ary, serializer_opts).as_json else serializer.new(obj, serializer_opts).as_json end end # This is odd, but it seems that in Rails `render json: obj` is about # 20% slower than calling MultiJSON.dump ourselves. I'm not sure why # Rails doesn't call MultiJson.dump when you pass it json: obj but # it seems we don't need whatever Rails is doing. def render_serialized(obj, serializer, opts = nil) render_json_dump(serialize_data(obj, serializer, opts), opts) end def render_json_dump(obj, opts = nil) opts ||= {} if opts[:rest_serializer] obj["__rest_serializer"] = "1" opts.each { |k, v| obj[k] = v if k.to_s.start_with?("refresh_") } obj["extras"] = opts[:extras] if opts[:extras] obj["meta"] = opts[:meta] if opts[:meta] end render json: MultiJson.dump(obj), status: opts[:status] || 200 end
app/controllers/application_controller.rb · lines 488–518guardian (so each object is serialized under the current user's permissions), then hands the result to render_json_dump, which dumps JSON manually via MultiJson rather than Rails' render json: for performance.Check your understanding
Answered in place — nothing is graded, everything is explained. 0 / 3 passed
Which before_action actually raises Discourse::ReadOnly to stop a mutating request while the site is in readonly mode?
What does ApplicationController do when a non-API request fails Rails' CSRF check?
Where do controller actions get their authorization checks and serializer scoping from?