2013-10-09 17:22:41 +11:00
require 'current_user'
2013-10-09 15:10:37 +11:00
require_dependency 'canonical_url'
2013-02-05 14:16:51 -05:00
require_dependency 'discourse'
require_dependency 'custom_renderer'
2013-10-09 15:10:37 +11:00
require_dependency 'archetype'
2013-02-05 14:16:51 -05:00
require_dependency 'rate_limiter'
class ApplicationController < ActionController :: Base
include CurrentUser
2013-02-13 19:04:43 +08:00
include CanonicalURL :: ControllerExtensions
2013-02-05 14:16:51 -05:00
serialization_scope :guardian
protect_from_forgery
2013-07-29 15:13:13 +10:00
# 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?
super
clear_current_user
2013-08-27 15:56:12 +10:00
render text : " ['BAD CSRF'] " , status : 403
2013-07-29 15:13:13 +10:00
end
end
2013-12-04 20:49:54 +09:00
before_filter :set_locale
2013-12-18 14:47:22 -05:00
before_filter :set_mobile_view
2013-02-05 14:16:51 -05:00
before_filter :inject_preview_style
2013-11-12 18:13:17 +01:00
before_filter :disable_customization
2013-02-05 14:16:51 -05:00
before_filter :block_if_maintenance_mode
before_filter :authorize_mini_profiler
before_filter :store_incoming_links
before_filter :preload_json
before_filter :check_xhr
2013-06-04 15:32:36 -07:00
before_filter :redirect_to_login_if_required
2013-02-07 16:45:24 +01:00
2013-02-05 14:16:51 -05:00
rescue_from Exception do | exception |
2013-11-12 18:13:17 +01:00
unless [ ActiveRecord :: RecordNotFound ,
ActionController :: RoutingError ,
ActionController :: UnknownController ,
AbstractController :: ActionNotFound ] . include? exception . class
2013-02-07 16:45:24 +01:00
begin
2013-02-05 14:16:51 -05:00
ErrorLog . report_async! ( exception , self , request , current_user )
2013-02-07 16:45:24 +01:00
rescue
2013-02-05 14:16:51 -05:00
# dont care give up
end
end
raise
end
2013-02-07 16:45:24 +01:00
2013-02-05 14:16:51 -05:00
# Some exceptions
class RenderEmpty < 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
2013-02-07 16:45:24 +01:00
time_left = I18n . t ( " rate_limiter.minutes " , count : ( e . available_in / 1 . minute . to_i ) )
2013-02-05 14:16:51 -05:00
else
2013-02-07 16:45:24 +01:00
time_left = I18n . t ( " rate_limiter.hours " , count : ( e . available_in / 1 . hour . to_i ) )
2013-02-05 14:16:51 -05:00
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?
2013-07-01 20:00:06 +02:00
redirect_to " / "
2013-02-05 14:16:51 -05:00
end
rescue_from Discourse :: NotFound do
2014-01-07 10:32:09 -05:00
rescue_discourse_actions ( " [error: 'not found'] " , 404 ) # TODO: this breaks json responses
2013-02-05 14:16:51 -05:00
end
rescue_from Discourse :: InvalidAccess do
2014-01-07 10:32:09 -05:00
rescue_discourse_actions ( " [error: 'invalid access'] " , 403 ) # TODO: this breaks json responses
2013-06-20 21:17:33 +05:30
end
def rescue_discourse_actions ( message , error )
2013-05-30 14:46:02 -04:00
if request . format && request . format . json?
2014-01-07 10:32:09 -05:00
# TODO: this doesn't make sense. Stuffing an html page into a json response will cause
# $.parseJSON to fail in the browser. Also returning text like "[error: 'invalid access']"
# from the above rescue_from blocks will fail because that isn't valid json.
2013-07-11 16:38:46 -04:00
render status : error , layout : false , text : ( error == 404 ) ? build_not_found_page ( error ) : message
2013-05-30 14:46:02 -04:00
else
2013-07-11 16:38:46 -04:00
render text : build_not_found_page ( error , 'no_js' )
2013-05-30 14:46:02 -04:00
end
2013-02-05 14:16:51 -05:00
end
2013-02-28 14:31:39 -05:00
def set_locale
I18n . locale = SiteSetting . default_locale
end
2013-02-05 14:16:51 -05:00
def store_preloaded ( key , json )
@preloaded || = { }
2013-02-25 19:42:20 +03:00
# I dislike that there is a gsub as opposed to a gsub!
# but we can not be mucking with user input, I wonder if there is a way
2013-02-11 17:28:21 +11:00
# to inject this safty deeper in the library or even in AM serializer
@preloaded [ key ] = json . gsub ( " </ " , " < \\ / " )
2013-02-05 14:16:51 -05:00
end
# If we are rendering HTML, preload the session data
def preload_json
2013-06-04 12:56:12 -04:00
# We don't preload JSON on xhr or JSON request
return if request . xhr?
2013-02-05 14:16:51 -05:00
2013-08-06 06:25:44 +10:00
preload_anonymous_data
2013-06-04 12:56:12 -04:00
2013-08-06 06:25:44 +10:00
if current_user
preload_current_user_data
current_user . sync_notification_channel_position
2013-02-05 14:16:51 -05:00
end
end
2013-12-18 14:47:22 -05:00
def set_mobile_view
session [ :mobile_view ] = params [ :mobile_view ] if params . has_key? ( :mobile_view )
end
2013-02-05 14:16:51 -05:00
def inject_preview_style
style = request [ 'preview-style' ]
2013-02-07 16:45:24 +01:00
session [ :preview_style ] = style if style
2013-02-05 14:16:51 -05:00
end
2013-11-12 18:13:17 +01:00
def disable_customization
session [ :disable_customization ] = params [ :customization ] == " 0 " if params . has_key? ( :customization )
end
2013-02-05 14:16:51 -05:00
def guardian
@guardian || = Guardian . new ( current_user )
end
2013-05-29 16:49:34 -04:00
def serialize_data ( obj , serializer , opts = { } )
2013-02-05 14:16:51 -05:00
# If it's an array, apply the serializer as an each_serializer to the elements
serializer_opts = { scope : guardian } . merge! ( opts )
2013-11-03 10:41:38 +05:30
if obj . is_a? ( Array ) or obj . is_a? ( ActiveRecord :: Associations :: CollectionProxy )
2013-02-05 14:16:51 -05:00
serializer_opts [ :each_serializer ] = serializer
2013-05-29 16:49:34 -04:00
ActiveModel :: ArraySerializer . new ( obj , serializer_opts ) . as_json
2013-02-07 16:45:24 +01:00
else
2013-05-29 16:49:34 -04:00
serializer . new ( obj , serializer_opts ) . as_json
2013-02-05 14:16:51 -05:00
end
2013-05-29 16:49:34 -04:00
end
2013-02-05 14:16:51 -05:00
2013-05-29 16:49:34 -04:00
# 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 = { } )
render_json_dump ( serialize_data ( obj , serializer , opts ) )
2013-02-05 14:16:51 -05:00
end
def render_json_dump ( obj )
render json : MultiJson . dump ( obj )
end
2013-02-06 11:55:54 -05:00
def can_cache_content?
2013-10-16 16:39:18 +11:00
! current_user . present?
2013-02-06 11:55:54 -05:00
end
# Our custom cache method
def discourse_expires_in ( time_length )
return unless can_cache_content?
2014-01-04 08:53:27 +01:00
Middleware :: AnonymousCache . anon_cache ( request . env , time_length )
2013-02-05 14:16:51 -05:00
end
2013-05-22 11:20:16 -04:00
def fetch_user_from_params
username_lower = params [ :username ] . downcase
username_lower . gsub! ( / \ .json$ / , '' )
user = User . where ( username_lower : username_lower ) . first
raise Discourse :: NotFound . new if user . blank?
guardian . ensure_can_see! ( user )
user
end
2013-09-04 11:53:00 -04:00
def post_ids_including_replies
post_ids = params [ :post_ids ] . map { | p | p . to_i }
if params [ :reply_post_ids ]
post_ids << PostReply . where ( post_id : params [ :reply_post_ids ] . map { | p | p . to_i } ) . pluck ( :reply_id )
post_ids . flatten!
post_ids . uniq!
end
post_ids
end
2013-02-05 14:16:51 -05:00
private
2013-08-06 06:25:44 +10:00
def preload_anonymous_data
store_preloaded ( " site " , Site . cached_json ( guardian ) )
store_preloaded ( " siteSettings " , SiteSetting . client_settings_json )
2013-12-17 18:49:22 +01:00
store_preloaded ( " customHTML " , custom_html_json )
2013-08-06 06:25:44 +10:00
end
def preload_current_user_data
2013-11-06 12:56:26 -05:00
store_preloaded ( " currentUser " , MultiJson . dump ( CurrentUserSerializer . new ( current_user , scope : guardian , root : false ) ) )
2013-08-06 06:25:44 +10:00
serializer = ActiveModel :: ArraySerializer . new ( TopicTrackingState . report ( [ current_user . id ] ) , each_serializer : TopicTrackingStateSerializer )
store_preloaded ( " topicTrackingStates " , MultiJson . dump ( serializer ) )
end
2013-12-17 18:49:22 +01:00
def custom_html_json
2013-12-17 18:25:27 +01:00
MultiJson . dump ( {
top : SiteContent . content_for ( :top ) ,
bottom : SiteContent . content_for ( :bottom ) ,
} )
end
2013-02-05 14:16:51 -05:00
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?
2013-04-12 10:07:46 +10:00
json [ obj . class . name . underscore ] = opts [ :serializer ] . new ( obj , scope : guardian ) . serializable_hash
2013-02-05 14:16:51 -05:00
end
render json : MultiJson . dump ( json )
else
render_json_error ( obj )
end
2013-02-07 16:45:24 +01:00
end
2013-02-05 14:16:51 -05:00
def block_if_maintenance_mode
if Discourse . maintenance_mode?
if request . format . json?
2013-04-07 18:14:50 +02:00
render status : 503 , json : failed_json . merge ( message : I18n . t ( 'site_under_maintenance' ) )
2013-02-05 14:16:51 -05:00
else
render status : 503 , file : File . join ( Rails . root , 'public' , '503.html' ) , layout : false
end
end
end
def mini_profiler_enabled?
2013-03-05 01:42:44 +01:00
defined? ( Rack :: MiniProfiler ) && current_user . try ( :admin? )
2013-02-05 14:16:51 -05:00
end
def authorize_mini_profiler
return unless mini_profiler_enabled?
Rack :: MiniProfiler . authorize_request
end
def store_incoming_links
2013-04-26 16:18:41 +10:00
IncomingLink . add ( request , current_user ) unless request . xhr?
2013-02-05 14:16:51 -05:00
end
2013-02-07 16:45:24 +01:00
def check_xhr
2013-08-06 06:25:44 +10:00
# bypass xhr check on PUT / POST / DELETE provided api key is there, otherwise calling api is annoying
return if ! request . get? && api_key_valid?
raise RenderEmpty . new unless ( ( request . format && request . format . json? ) || request . xhr? )
2013-02-05 14:16:51 -05:00
end
def ensure_logged_in
raise Discourse :: NotLoggedIn . new unless current_user . present?
end
2013-02-07 16:45:24 +01:00
2013-06-04 15:32:36 -07:00
def redirect_to_login_if_required
2014-01-24 13:47:35 +01:00
return if current_user || ( request . format . json? && api_key_valid? )
redirect_to :login if SiteSetting . login_required?
2013-06-04 15:32:36 -07:00
end
2013-07-11 16:38:46 -04:00
def build_not_found_page ( status = 404 , layout = false )
2013-11-13 12:26:32 -05:00
@top_viewed = Topic . top_viewed ( 10 )
@recent = Topic . recent ( 10 )
2013-05-30 14:46:02 -04:00
@slug = params [ :slug ] . class == String ? params [ :slug ] : ''
2013-06-06 14:41:27 -04:00
@slug = ( params [ :id ] . class == String ? params [ :id ] : '' ) if @slug . blank?
2013-05-30 14:46:02 -04:00
@slug . gsub! ( '-' , ' ' )
2013-07-11 16:38:46 -04:00
render_to_string status : status , layout : layout , formats : [ :html ] , template : '/exceptions/not_found'
2013-05-30 14:46:02 -04:00
end
2013-06-19 19:11:14 -07:00
protected
2013-06-17 16:09:59 +10:00
def api_key_valid?
2013-10-22 15:53:08 -04:00
request [ " api_key " ] && ApiKey . where ( key : request [ " api_key " ] ) . exists?
2013-06-17 16:09:59 +10:00
end
2013-06-19 19:11:14 -07:00
# returns an array of integers given a param key
# returns nil if key is not found
def param_to_integer_list ( key , delimiter = ',' )
if params [ key ]
params [ key ] . split ( delimiter ) . map ( & :to_i )
end
end
2013-02-05 14:16:51 -05:00
end