require 'current_user' require_dependency 'discourse' require_dependency 'custom_renderer' require 'archetype' require_dependency 'rate_limiter' class ApplicationController < ActionController::Base include CurrentUser serialization_scope :guardian protect_from_forgery before_filter :inject_preview_style before_filter :block_if_maintenance_mode before_filter :check_restricted_access before_filter :authorize_mini_profiler before_filter :store_incoming_links before_filter :preload_json before_filter :check_xhr rescue_from Exception do |exception| unless [ ActiveRecord::RecordNotFound, ActionController::RoutingError, ActionController::UnknownController, AbstractController::ActionNotFound].include? exception.class begin ErrorLog.report_async!(exception, self, request, current_user) rescue # dont care give up end end raise end # Some exceptions class RenderEmpty < Exception; end class NotLoggedIn < Exception; end # Render nothing unless we are an xhr request rescue_from RenderEmpty do render 'default/empty' end # If they hit the rate limiter rescue_from RateLimiter::LimitExceeded do |e| time_left = "" if e.available_in < 1.minute.to_i time_left = I18n.t("rate_limiter.seconds", count: e.available_in) elsif e.available_in < 1.hour.to_i time_left = I18n.t("rate_limiter.minutes", count: (e.available_in / 1.minute.to_i)) else time_left = I18n.t("rate_limiter.hours", count: (e.available_in / 1.hour.to_i)) end render json: {errors: [I18n.t("rate_limiter.too_many_requests", time_left: time_left)]}, status: 429 end rescue_from Discourse::NotLoggedIn do |e| raise e if Rails.env.test? redirect_to root_path end rescue_from Discourse::NotFound do if request.format.html? # for now do a simple remap, we may look at cleaner ways of doing the render raise ActiveRecord::RecordNotFound else render file: 'public/404', layout: false, status: 404 end end rescue_from Discourse::InvalidAccess do render file: 'public/403', layout: false, status: 403 end def store_preloaded(key, json) @preloaded ||= {} @preloaded[key] = json end # If we are rendering HTML, preload the session data def preload_json if request.format.html? if guardian.current_user guardian.current_user.sync_notification_channel_position end store_preloaded("site", Site.cached_json) if current_user.present? store_preloaded("currentUser", MultiJson.dump(CurrentUserSerializer.new(current_user, root: false))) end store_preloaded("siteSettings", SiteSetting.client_settings_json) end end def inject_preview_style style = request['preview-style'] session[:preview_style] = style if style end def guardian @guardian ||= Guardian.new(current_user) end def log_on_user(user) session[:current_user_id] = user.id unless user.auth_token user.auth_token = SecureRandom.hex(16) user.save! end cookies.permanent[:_t] = user.auth_token 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={}) # If it's an array, apply the serializer as an each_serializer to the elements serializer_opts = {scope: guardian}.merge!(opts) if obj.is_a?(Array) serializer_opts[:each_serializer] = serializer render_json_dump(ActiveModel::ArraySerializer.new(obj, serializer_opts).as_json) else render_json_dump(serializer.new(obj, serializer_opts).as_json) end end def render_json_dump(obj) render json: MultiJson.dump(obj) end # Helper method - if no logged in user (anonymous), use Rails' conditional GET # support. Should be very fast behind a cache. def anonymous_etag(*args) if current_user.blank? and Rails.env.production? yield if stale?(*args) # Add a one minute expiry expires_in 1.minute, :public => true else yield end end private def render_json_error(obj) if obj.present? render json: MultiJson.dump(errors: obj.errors.full_messages), status: 422 else render json: MultiJson.dump(errors: [I18n.t('js.generic_error')]), status: 422 end end def success_json {success: 'OK'} end def failed_json {failed: 'FAILED'} end def json_result(obj, opts={}) if yield(obj) json = success_json # If we were given a serializer, add the class to the json that comes back if opts[:serializer].present? json[obj.class.name.underscore] = opts[:serializer].new(obj).serializable_hash end render json: MultiJson.dump(json) else render_json_error(obj) end end def block_if_maintenance_mode if Discourse.maintenance_mode? if request.format.json? render status: 503, json: failed_json.merge( message: 'Site is currently undergoing maintenance.' ) else render status: 503, file: File.join( Rails.root, 'public', '503.html' ), layout: false end end end def check_restricted_access # note current_user is defined in the CurrentUser mixin if SiteSetting.restrict_access? && cookies[:_access] != SiteSetting.access_password redirect_to request_access_path(:return_path => request.fullpath) return false end end def mini_profiler_enabled? defined?(Rack::MiniProfiler) and current_user.try(:admin?) end def authorize_mini_profiler return unless mini_profiler_enabled? Rack::MiniProfiler.authorize_request end def requires_parameters(*required) required.each do |p| raise Discourse::InvalidParameters.new(p) unless params.has_key?(p) end end alias :requires_parameter :requires_parameters def store_incoming_links if request.referer.present? parsed = URI.parse(request.referer) if parsed.host != request.host IncomingLink.create(url: request.url, referer: request.referer[0..999]) end end end def check_xhr unless (controller_name == 'forums' || controller_name == 'user_open_ids') # render 'default/empty' unless ((request.format && request.format.json?) or request.xhr?) raise RenderEmpty.new unless ((request.format && request.format.json?) or request.xhr?) end end def ensure_logged_in raise Discourse::NotLoggedIn.new unless current_user.present? end end