forked from e621ng/e621ng
240 lines
7.8 KiB
Ruby
240 lines
7.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class ApplicationController < ActionController::Base
|
|
class APIThrottled < Exception; end
|
|
class FeatureUnavailable < StandardError; end
|
|
|
|
skip_forgery_protection if: -> { SessionLoader.new(request).has_api_authentication? || request.options? }
|
|
before_action :reset_current_user
|
|
before_action :set_current_user
|
|
before_action :normalize_search
|
|
before_action :api_check
|
|
before_action :enable_cors
|
|
before_action :check_valid_username
|
|
after_action :reset_current_user
|
|
layout "default"
|
|
|
|
include TitleHelper
|
|
include DeferredPosts
|
|
helper_method :deferred_post_ids, :deferred_posts
|
|
|
|
rescue_from Exception, :with => :rescue_exception
|
|
rescue_from User::PrivilegeError, :with => :access_denied
|
|
rescue_from ActionController::UnpermittedParameters, :with => :access_denied
|
|
|
|
# This is raised on requests to `/blah.js`. Rails has already rendered StaticController#not_found
|
|
# here, so calling `rescue_exception` would cause a double render error.
|
|
rescue_from ActionController::InvalidCrossOriginRequest, with: -> {}
|
|
|
|
def enable_cors
|
|
response.headers["Access-Control-Allow-Origin"] = "*"
|
|
response.headers["Access-Control-Allow-Headers"] = "Authorization"
|
|
end
|
|
|
|
def check_valid_username
|
|
return if params[:controller] == "user_name_change_requests"
|
|
|
|
if request.format.html? && CurrentUser.user.name_error
|
|
redirect_to new_user_name_change_request_path
|
|
end
|
|
end
|
|
|
|
protected
|
|
|
|
def api_check
|
|
if !CurrentUser.is_anonymous? && !request.get? && !request.head?
|
|
throttled = CurrentUser.user.token_bucket.throttled?
|
|
headers["X-Api-Limit"] = CurrentUser.user.token_bucket.cached_count.to_s
|
|
|
|
if throttled
|
|
raise APIThrottled.new
|
|
return false
|
|
end
|
|
end
|
|
|
|
true
|
|
end
|
|
|
|
def rescue_exception(exception)
|
|
@exception = exception
|
|
|
|
# If InvalidAuthenticityToken was raised, CurrentUser isn't set so we have to do it here manually.
|
|
CurrentUser.user ||= User.anonymous
|
|
|
|
case exception
|
|
when ProcessingError
|
|
render_expected_error(400, exception)
|
|
when APIThrottled
|
|
render_expected_error(429, "Throttled: Too many requests")
|
|
when ActiveRecord::QueryCanceled
|
|
render_error_page(500, exception, message: "The database timed out running your query.")
|
|
when ActionController::BadRequest, PostVersion::UndoError
|
|
render_error_page(400, exception)
|
|
when SessionLoader::AuthenticationFailure
|
|
session.delete(:user_id)
|
|
cookies.delete :remember
|
|
render_expected_error(401, exception.message)
|
|
when ActionController::InvalidAuthenticityToken
|
|
render_expected_error(403, "ActionController::InvalidAuthenticityToken. Did you properly authorize your request?")
|
|
when ActiveRecord::RecordNotFound
|
|
render_404
|
|
when ActionController::RoutingError
|
|
render_error_page(405, exception)
|
|
when ActionController::UnknownFormat, ActionView::MissingTemplate
|
|
render_unsupported_format
|
|
when Danbooru::Paginator::PaginationError
|
|
render_expected_error(410, exception.message)
|
|
when TagQuery::CountExceededError
|
|
render_expected_error(422, exception.message)
|
|
when FeatureUnavailable
|
|
render_expected_error(400, "This feature isn't available")
|
|
when PG::ConnectionBad
|
|
render_error_page(503, exception, message: "The database is unavailable. Try again later.")
|
|
when ActionController::ParameterMissing
|
|
render_expected_error(400, exception.message)
|
|
when BCrypt::Errors::InvalidHash
|
|
render_expected_error(400, "You must reset your password.")
|
|
else
|
|
render_error_page(500, exception)
|
|
end
|
|
end
|
|
|
|
def render_404
|
|
respond_to do |fmt|
|
|
fmt.html do
|
|
render "static/404", formats: [:html, :atom], status: 404
|
|
end
|
|
fmt.json do
|
|
render json: { success: false, reason: "not found" }, status: 404
|
|
end
|
|
fmt.any do
|
|
render_unsupported_format
|
|
end
|
|
end
|
|
end
|
|
|
|
def render_unsupported_format
|
|
render_expected_error(406, "#{request.format} is not a supported format for this page", format: :html)
|
|
end
|
|
|
|
def render_expected_error(status, message, format: request.format.symbol)
|
|
format = :html unless format.in?(%i[html json atom])
|
|
@message = message
|
|
render "static/error", status: status, formats: format
|
|
end
|
|
|
|
def render_error_page(status, exception, message: exception.message, format: request.format.symbol)
|
|
@exception = exception
|
|
@expected = status < 500
|
|
@message = message.encode("utf-8", invalid: :replace, undef: :replace )
|
|
@backtrace = Rails.backtrace_cleaner.clean(@exception.backtrace)
|
|
format = :html unless format.in?(%i[html json atom])
|
|
|
|
if !CurrentUser.user.is_janitor? && message == exception.message
|
|
@message = "An unexpected error occurred."
|
|
end
|
|
|
|
DanbooruLogger.log(@exception, expected: @expected)
|
|
log = ExceptionLog.add(exception, CurrentUser.id, request) if !@expected
|
|
@log_code = log&.code
|
|
render "static/error", status: status, formats: format
|
|
end
|
|
|
|
def access_denied(exception = nil)
|
|
previous_url = params[:url] || request.fullpath
|
|
@message = "Access Denied: #{exception}" if exception.is_a?(String)
|
|
@message ||= exception&.message || "Access Denied"
|
|
|
|
respond_to do |fmt|
|
|
fmt.html do
|
|
if CurrentUser.is_anonymous?
|
|
if request.get?
|
|
redirect_to new_session_path(:url => previous_url), notice: @message
|
|
else
|
|
redirect_to new_session_path, notice: @message
|
|
end
|
|
else
|
|
render :template => "static/access_denied", :status => 403
|
|
end
|
|
end
|
|
fmt.json do
|
|
render :json => {:success => false, reason: @message}.to_json, :status => 403
|
|
end
|
|
end
|
|
end
|
|
|
|
def set_current_user
|
|
SessionLoader.new(request).load
|
|
session.send(:load!) unless session.send(:loaded?)
|
|
end
|
|
|
|
def reset_current_user
|
|
CurrentUser.user = nil
|
|
CurrentUser.ip_addr = nil
|
|
CurrentUser.safe_mode = Danbooru.config.safe_mode?
|
|
end
|
|
|
|
def requires_reauthentication
|
|
return redirect_to(new_session_path(url: request.fullpath)) if CurrentUser.user.is_anonymous?
|
|
last_authenticated_at = session[:last_authenticated_at]
|
|
if last_authenticated_at.blank? || Time.zone.parse(last_authenticated_at) < 1.hour.ago
|
|
redirect_to(confirm_password_session_path(url: request.fullpath))
|
|
end
|
|
end
|
|
|
|
def user_access_check(method)
|
|
if !CurrentUser.user.send(method) || CurrentUser.user.is_banned? || IpBan.is_banned?(CurrentUser.ip_addr)
|
|
access_denied
|
|
end
|
|
end
|
|
|
|
User::Roles.each do |role|
|
|
define_method("#{role}_only") do
|
|
user_access_check("is_#{role}?")
|
|
end
|
|
end
|
|
|
|
%i[is_bd_staff can_view_staff_notes can_handle_takedowns can_edit_avoid_posting_entries].each do |role|
|
|
define_method("#{role}_only") do
|
|
user_access_check("#{role}?")
|
|
end
|
|
end
|
|
|
|
def logged_in_only
|
|
if CurrentUser.is_anonymous?
|
|
access_denied("Must be logged in")
|
|
end
|
|
end
|
|
|
|
# Remove blank `search` params from the url.
|
|
#
|
|
# /tags?search[name]=touhou&search[category]=&search[order]=
|
|
# => /tags?search[name]=touhou
|
|
def normalize_search
|
|
return unless request.get? || request.head?
|
|
params[:search] ||= ActionController::Parameters.new
|
|
|
|
deep_reject_blank = ->(hash) do
|
|
hash.reject { |k, v| v.blank? || (v.is_a?(Hash) && deep_reject_blank.call(v).blank?) }
|
|
end
|
|
if params[:search].is_a?(ActionController::Parameters)
|
|
nonblank_search_params = deep_reject_blank.call(params[:search])
|
|
else
|
|
nonblank_search_params = ActionController::Parameters.new
|
|
end
|
|
|
|
if nonblank_search_params != params[:search]
|
|
params[:search] = nonblank_search_params
|
|
redirect_to url_for(params: params.except(:controller, :action, :index).permit!)
|
|
end
|
|
end
|
|
|
|
def search_params
|
|
params.fetch(:search, {}).permit!
|
|
end
|
|
|
|
def permit_search_params(permitted_params)
|
|
params.fetch(:search, {}).permit([:id, :created_at, :updated_at] + permitted_params)
|
|
end
|
|
end
|