mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
DEV: Apply syntax_tree formatting to lib/*
This commit is contained in:
@@ -7,17 +7,16 @@ require "http_language_parser"
|
||||
|
||||
module Middleware
|
||||
class AnonymousCache
|
||||
|
||||
def self.cache_key_segments
|
||||
@@cache_key_segments ||= {
|
||||
m: 'key_is_mobile?',
|
||||
c: 'key_is_crawler?',
|
||||
o: 'key_is_old_browser?',
|
||||
d: 'key_is_modern_mobile_device?',
|
||||
b: 'key_has_brotli?',
|
||||
t: 'key_cache_theme_ids',
|
||||
ca: 'key_compress_anon',
|
||||
l: 'key_locale'
|
||||
m: "key_is_mobile?",
|
||||
c: "key_is_crawler?",
|
||||
o: "key_is_old_browser?",
|
||||
d: "key_is_modern_mobile_device?",
|
||||
b: "key_has_brotli?",
|
||||
t: "key_cache_theme_ids",
|
||||
ca: "key_compress_anon",
|
||||
l: "key_locale",
|
||||
}
|
||||
end
|
||||
|
||||
@@ -46,9 +45,9 @@ module Middleware
|
||||
|
||||
# This gives us an API to insert anonymous cache segments
|
||||
class Helper
|
||||
RACK_SESSION = "rack.session"
|
||||
USER_AGENT = "HTTP_USER_AGENT"
|
||||
ACCEPT_ENCODING = "HTTP_ACCEPT_ENCODING"
|
||||
RACK_SESSION = "rack.session"
|
||||
USER_AGENT = "HTTP_USER_AGENT"
|
||||
ACCEPT_ENCODING = "HTTP_ACCEPT_ENCODING"
|
||||
DISCOURSE_RENDER = "HTTP_DISCOURSE_RENDER"
|
||||
|
||||
REDIS_STORE_SCRIPT = DiscourseRedis::EvalHelper.new <<~LUA
|
||||
@@ -63,13 +62,11 @@ module Middleware
|
||||
end
|
||||
|
||||
def blocked_crawler?
|
||||
@request.get? &&
|
||||
!@request.xhr? &&
|
||||
!@request.path.ends_with?('robots.txt') &&
|
||||
!@request.path.ends_with?('srv/status') &&
|
||||
@request[Auth::DefaultCurrentUserProvider::API_KEY].nil? &&
|
||||
@env[Auth::DefaultCurrentUserProvider::USER_API_KEY].nil? &&
|
||||
CrawlerDetection.is_blocked_crawler?(@env[USER_AGENT])
|
||||
@request.get? && !@request.xhr? && !@request.path.ends_with?("robots.txt") &&
|
||||
!@request.path.ends_with?("srv/status") &&
|
||||
@request[Auth::DefaultCurrentUserProvider::API_KEY].nil? &&
|
||||
@env[Auth::DefaultCurrentUserProvider::USER_API_KEY].nil? &&
|
||||
CrawlerDetection.is_blocked_crawler?(@env[USER_AGENT])
|
||||
end
|
||||
|
||||
def is_mobile=(val)
|
||||
@@ -112,10 +109,16 @@ module Middleware
|
||||
begin
|
||||
user_agent = @env[USER_AGENT]
|
||||
|
||||
if @env[DISCOURSE_RENDER] == "crawler" || CrawlerDetection.crawler?(user_agent, @env["HTTP_VIA"])
|
||||
if @env[DISCOURSE_RENDER] == "crawler" ||
|
||||
CrawlerDetection.crawler?(user_agent, @env["HTTP_VIA"])
|
||||
:true
|
||||
else
|
||||
user_agent.downcase.include?("discourse") && !user_agent.downcase.include?("mobile") ? :true : :false
|
||||
if user_agent.downcase.include?("discourse") &&
|
||||
!user_agent.downcase.include?("mobile")
|
||||
:true
|
||||
else
|
||||
:false
|
||||
end
|
||||
end
|
||||
end
|
||||
@is_crawler == :true
|
||||
@@ -133,13 +136,14 @@ module Middleware
|
||||
def cache_key
|
||||
return @cache_key if defined?(@cache_key)
|
||||
|
||||
@cache_key = +"ANON_CACHE_#{@env["HTTP_ACCEPT"]}_#{@env[Rack::RACK_URL_SCHEME]}_#{@env["HTTP_HOST"]}#{@env["REQUEST_URI"]}"
|
||||
@cache_key =
|
||||
+"ANON_CACHE_#{@env["HTTP_ACCEPT"]}_#{@env[Rack::RACK_URL_SCHEME]}_#{@env["HTTP_HOST"]}#{@env["REQUEST_URI"]}"
|
||||
@cache_key << AnonymousCache.build_cache_key(self)
|
||||
@cache_key
|
||||
end
|
||||
|
||||
def key_cache_theme_ids
|
||||
theme_ids.join(',')
|
||||
theme_ids.join(",")
|
||||
end
|
||||
|
||||
def key_compress_anon
|
||||
@@ -147,7 +151,7 @@ module Middleware
|
||||
end
|
||||
|
||||
def theme_ids
|
||||
ids, _ = @request.cookies['theme_ids']&.split('|')
|
||||
ids, _ = @request.cookies["theme_ids"]&.split("|")
|
||||
id = ids&.split(",")&.map(&:to_i)&.first
|
||||
if id && Guardian.new.allow_themes?([id])
|
||||
Theme.transform_ids(id)
|
||||
@@ -178,31 +182,31 @@ module Middleware
|
||||
|
||||
def no_cache_bypass
|
||||
request = Rack::Request.new(@env)
|
||||
request.cookies['_bypass_cache'].nil? &&
|
||||
(request.path != '/srv/status') &&
|
||||
request.cookies["_bypass_cache"].nil? && (request.path != "/srv/status") &&
|
||||
request[Auth::DefaultCurrentUserProvider::API_KEY].nil? &&
|
||||
@env[Auth::DefaultCurrentUserProvider::USER_API_KEY].nil?
|
||||
end
|
||||
|
||||
def force_anonymous!
|
||||
@env[Auth::DefaultCurrentUserProvider::USER_API_KEY] = nil
|
||||
@env['HTTP_COOKIE'] = nil
|
||||
@env['HTTP_DISCOURSE_LOGGED_IN'] = nil
|
||||
@env['rack.request.cookie.hash'] = {}
|
||||
@env['rack.request.cookie.string'] = ''
|
||||
@env['_bypass_cache'] = nil
|
||||
@env["HTTP_COOKIE"] = nil
|
||||
@env["HTTP_DISCOURSE_LOGGED_IN"] = nil
|
||||
@env["rack.request.cookie.hash"] = {}
|
||||
@env["rack.request.cookie.string"] = ""
|
||||
@env["_bypass_cache"] = nil
|
||||
request = Rack::Request.new(@env)
|
||||
request.delete_param('api_username')
|
||||
request.delete_param('api_key')
|
||||
request.delete_param("api_username")
|
||||
request.delete_param("api_key")
|
||||
end
|
||||
|
||||
def logged_in_anon_limiter
|
||||
@logged_in_anon_limiter ||= RateLimiter.new(
|
||||
nil,
|
||||
"logged_in_anon_cache_#{@env["HTTP_HOST"]}/#{@env["REQUEST_URI"]}",
|
||||
GlobalSetting.force_anonymous_min_per_10_seconds,
|
||||
10
|
||||
)
|
||||
@logged_in_anon_limiter ||=
|
||||
RateLimiter.new(
|
||||
nil,
|
||||
"logged_in_anon_cache_#{@env["HTTP_HOST"]}/#{@env["REQUEST_URI"]}",
|
||||
GlobalSetting.force_anonymous_min_per_10_seconds,
|
||||
10,
|
||||
)
|
||||
end
|
||||
|
||||
def check_logged_in_rate_limit!
|
||||
@@ -213,13 +217,11 @@ module Middleware
|
||||
ADP = "action_dispatch.request.parameters"
|
||||
|
||||
def should_force_anonymous?
|
||||
if (queue_time = @env['REQUEST_QUEUE_SECONDS']) && get?
|
||||
if (queue_time = @env["REQUEST_QUEUE_SECONDS"]) && get?
|
||||
if queue_time > GlobalSetting.force_anonymous_min_queue_seconds
|
||||
return check_logged_in_rate_limit!
|
||||
elsif queue_time >= MIN_TIME_TO_CHECK
|
||||
if !logged_in_anon_limiter.can_perform?
|
||||
return check_logged_in_rate_limit!
|
||||
end
|
||||
return check_logged_in_rate_limit! if !logged_in_anon_limiter.can_perform?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -233,7 +235,7 @@ module Middleware
|
||||
def compress(val)
|
||||
if val && GlobalSetting.compress_anon_cache
|
||||
require "lz4-ruby" if !defined?(LZ4)
|
||||
LZ4::compress(val)
|
||||
LZ4.compress(val)
|
||||
else
|
||||
val
|
||||
end
|
||||
@@ -242,7 +244,7 @@ module Middleware
|
||||
def decompress(val)
|
||||
if val && GlobalSetting.compress_anon_cache
|
||||
require "lz4-ruby" if !defined?(LZ4)
|
||||
LZ4::uncompress(val)
|
||||
LZ4.uncompress(val)
|
||||
else
|
||||
val
|
||||
end
|
||||
@@ -273,7 +275,6 @@ module Middleware
|
||||
status, headers, response = result
|
||||
|
||||
if status == 200 && cache_duration
|
||||
|
||||
if GlobalSetting.anon_cache_store_threshold > 1
|
||||
count = REDIS_STORE_SCRIPT.eval(Discourse.redis, [cache_key_count], [cache_duration])
|
||||
|
||||
@@ -281,25 +282,24 @@ module Middleware
|
||||
# prudent here, hence the to_i
|
||||
if count.to_i < GlobalSetting.anon_cache_store_threshold
|
||||
headers["X-Discourse-Cached"] = "skip"
|
||||
return [status, headers, response]
|
||||
return status, headers, response
|
||||
end
|
||||
end
|
||||
|
||||
headers_stripped = headers.dup.delete_if { |k, _| ["Set-Cookie", "X-MiniProfiler-Ids"].include? k }
|
||||
headers_stripped =
|
||||
headers.dup.delete_if { |k, _| %w[Set-Cookie X-MiniProfiler-Ids].include? k }
|
||||
headers_stripped["X-Discourse-Cached"] = "true"
|
||||
parts = []
|
||||
response.each do |part|
|
||||
parts << part
|
||||
end
|
||||
response.each { |part| parts << part }
|
||||
|
||||
if req_params = env[ADP]
|
||||
headers_stripped[ADP] = {
|
||||
"action" => req_params["action"],
|
||||
"controller" => req_params["controller"]
|
||||
"controller" => req_params["controller"],
|
||||
}
|
||||
end
|
||||
|
||||
Discourse.redis.setex(cache_key_body, cache_duration, compress(parts.join))
|
||||
Discourse.redis.setex(cache_key_body, cache_duration, compress(parts.join))
|
||||
Discourse.redis.setex(cache_key_other, cache_duration, [status, headers_stripped].to_json)
|
||||
|
||||
headers["X-Discourse-Cached"] = "store"
|
||||
@@ -314,20 +314,18 @@ module Middleware
|
||||
Discourse.redis.del(cache_key_body)
|
||||
Discourse.redis.del(cache_key_other)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def initialize(app, settings = {})
|
||||
@app = app
|
||||
end
|
||||
|
||||
PAYLOAD_INVALID_REQUEST_METHODS = ["GET", "HEAD"]
|
||||
PAYLOAD_INVALID_REQUEST_METHODS = %w[GET HEAD]
|
||||
|
||||
def call(env)
|
||||
if PAYLOAD_INVALID_REQUEST_METHODS.include?(env[Rack::REQUEST_METHOD]) &&
|
||||
env[Rack::RACK_INPUT].size > 0
|
||||
|
||||
return [413, { "Cache-Control" => "private, max-age=0, must-revalidate" }, []]
|
||||
env[Rack::RACK_INPUT].size > 0
|
||||
return 413, { "Cache-Control" => "private, max-age=0, must-revalidate" }, []
|
||||
end
|
||||
|
||||
helper = Helper.new(env)
|
||||
@@ -335,7 +333,7 @@ module Middleware
|
||||
|
||||
if helper.blocked_crawler?
|
||||
env["discourse.request_tracker.skip"] = true
|
||||
return [403, {}, ["Crawler is not allowed!"]]
|
||||
return 403, {}, ["Crawler is not allowed!"]
|
||||
end
|
||||
|
||||
if helper.should_force_anonymous?
|
||||
@@ -348,15 +346,15 @@ module Middleware
|
||||
if max_time > 0 && queue_time.to_f > max_time
|
||||
return [
|
||||
429,
|
||||
{
|
||||
"content-type" => "application/json; charset=utf-8"
|
||||
},
|
||||
[{
|
||||
errors: I18n.t("rate_limiter.slow_down"),
|
||||
extras: {
|
||||
wait_seconds: 5 + (5 * rand).round(2)
|
||||
}
|
||||
}.to_json]
|
||||
{ "content-type" => "application/json; charset=utf-8" },
|
||||
[
|
||||
{
|
||||
errors: I18n.t("rate_limiter.slow_down"),
|
||||
extras: {
|
||||
wait_seconds: 5 + (5 * rand).round(2),
|
||||
},
|
||||
}.to_json,
|
||||
]
|
||||
]
|
||||
end
|
||||
end
|
||||
@@ -368,13 +366,9 @@ module Middleware
|
||||
@app.call(env)
|
||||
end
|
||||
|
||||
if force_anon
|
||||
result[1]["Set-Cookie"] = "dosp=1; Path=/"
|
||||
end
|
||||
result[1]["Set-Cookie"] = "dosp=1; Path=/" if force_anon
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -4,11 +4,14 @@
|
||||
# we need to handle certain exceptions here
|
||||
module Middleware
|
||||
class DiscoursePublicExceptions < ::ActionDispatch::PublicExceptions
|
||||
INVALID_REQUEST_ERRORS = Set.new([
|
||||
Rack::QueryParser::InvalidParameterError,
|
||||
ActionController::BadRequest,
|
||||
ActionDispatch::Http::Parameters::ParseError,
|
||||
])
|
||||
INVALID_REQUEST_ERRORS =
|
||||
Set.new(
|
||||
[
|
||||
Rack::QueryParser::InvalidParameterError,
|
||||
ActionController::BadRequest,
|
||||
ActionDispatch::Http::Parameters::ParseError,
|
||||
],
|
||||
)
|
||||
|
||||
def initialize(path)
|
||||
super
|
||||
@@ -35,31 +38,38 @@ module Middleware
|
||||
begin
|
||||
request.format
|
||||
rescue Mime::Type::InvalidMimeType
|
||||
return [400, { "Cache-Control" => "private, max-age=0, must-revalidate" }, ["Invalid MIME type"]]
|
||||
return [
|
||||
400,
|
||||
{ "Cache-Control" => "private, max-age=0, must-revalidate" },
|
||||
["Invalid MIME type"]
|
||||
]
|
||||
end
|
||||
|
||||
# Or badly formatted multipart requests
|
||||
begin
|
||||
request.POST
|
||||
rescue EOFError
|
||||
return [400, { "Cache-Control" => "private, max-age=0, must-revalidate" }, ["Invalid request"]]
|
||||
return [
|
||||
400,
|
||||
{ "Cache-Control" => "private, max-age=0, must-revalidate" },
|
||||
["Invalid request"]
|
||||
]
|
||||
end
|
||||
|
||||
if ApplicationController.rescue_with_handler(exception, object: fake_controller)
|
||||
body = response.body
|
||||
if String === body
|
||||
body = [body]
|
||||
end
|
||||
return [response.status, response.headers, body]
|
||||
body = [body] if String === body
|
||||
return response.status, response.headers, body
|
||||
end
|
||||
rescue => e
|
||||
return super if INVALID_REQUEST_ERRORS.include?(e.class)
|
||||
Discourse.warn_exception(e, message: "Failed to handle exception in exception app middleware")
|
||||
Discourse.warn_exception(
|
||||
e,
|
||||
message: "Failed to handle exception in exception app middleware",
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,7 +18,8 @@ module Middleware
|
||||
requested_hostname = env[Rack::HTTP_HOST]
|
||||
|
||||
env[Discourse::REQUESTED_HOSTNAME] = requested_hostname
|
||||
env[Rack::HTTP_HOST] = allowed_hostnames.find { |h| h == requested_hostname } || Discourse.current_hostname_with_port
|
||||
env[Rack::HTTP_HOST] = allowed_hostnames.find { |h| h == requested_hostname } ||
|
||||
Discourse.current_hostname_with_port
|
||||
|
||||
@app.call(env)
|
||||
end
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Middleware
|
||||
|
||||
# In development mode, it is common to use a database from a production site for testing
|
||||
# with their data. Unfortunately, you can end up with dozens of missing avatar requests
|
||||
# due to the files not being present locally. This middleware, only enabled in development
|
||||
@@ -12,11 +11,11 @@ module Middleware
|
||||
end
|
||||
|
||||
def call(env)
|
||||
if (env['REQUEST_PATH'] =~ /^\/uploads\/default\/avatars/)
|
||||
path = "#{Rails.root}/public#{env['REQUEST_PATH']}"
|
||||
if (env["REQUEST_PATH"] =~ %r{^/uploads/default/avatars})
|
||||
path = "#{Rails.root}/public#{env["REQUEST_PATH"]}"
|
||||
unless File.exist?(path)
|
||||
default_image = "#{Rails.root}/public/images/d-logo-sketch-small.png"
|
||||
return [ 200, { 'Content-Type' => 'image/png' }, [ File.read(default_image)] ]
|
||||
return 200, { "Content-Type" => "image/png" }, [File.read(default_image)]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,5 +23,4 @@ module Middleware
|
||||
[status, headers, response]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -5,7 +5,8 @@ require "csrf_token_verifier"
|
||||
# omniauth loves spending lots cycles in its magic middleware stack
|
||||
# this middleware bypasses omniauth middleware and only hits it when needed
|
||||
class Middleware::OmniauthBypassMiddleware
|
||||
class AuthenticatorDisabled < StandardError; end
|
||||
class AuthenticatorDisabled < StandardError
|
||||
end
|
||||
|
||||
def initialize(app, options = {})
|
||||
@app = app
|
||||
@@ -15,11 +16,10 @@ class Middleware::OmniauthBypassMiddleware
|
||||
# if you need to test this and are having ssl issues see:
|
||||
# http://stackoverflow.com/questions/6756460/openssl-error-using-omniauth-specified-ssl-path-but-didnt-work
|
||||
# OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE if Rails.env.development?
|
||||
@omniauth = OmniAuth::Builder.new(app) do
|
||||
Discourse.authenticators.each do |authenticator|
|
||||
authenticator.register_middleware(self)
|
||||
@omniauth =
|
||||
OmniAuth::Builder.new(app) do
|
||||
Discourse.authenticators.each { |authenticator| authenticator.register_middleware(self) }
|
||||
end
|
||||
end
|
||||
|
||||
@omniauth.before_request_phase do |env|
|
||||
request = ActionDispatch::Request.new(env)
|
||||
@@ -28,7 +28,9 @@ class Middleware::OmniauthBypassMiddleware
|
||||
CSRFTokenVerifier.new.call(env) if request.request_method.downcase.to_sym != :get
|
||||
|
||||
# Check whether the authenticator is enabled
|
||||
if !Discourse.enabled_authenticators.any? { |a| a.name.to_sym == env['omniauth.strategy'].name.to_sym }
|
||||
if !Discourse.enabled_authenticators.any? { |a|
|
||||
a.name.to_sym == env["omniauth.strategy"].name.to_sym
|
||||
}
|
||||
raise AuthenticatorDisabled
|
||||
end
|
||||
|
||||
@@ -44,8 +46,9 @@ class Middleware::OmniauthBypassMiddleware
|
||||
if env["PATH_INFO"].start_with?("/auth")
|
||||
begin
|
||||
# When only one provider is enabled, assume it can be completely trusted, and allow GET requests
|
||||
only_one_provider = !SiteSetting.enable_local_logins && Discourse.enabled_authenticators.length == 1
|
||||
OmniAuth.config.allowed_request_methods = only_one_provider ? [:get, :post] : [:post]
|
||||
only_one_provider =
|
||||
!SiteSetting.enable_local_logins && Discourse.enabled_authenticators.length == 1
|
||||
OmniAuth.config.allowed_request_methods = only_one_provider ? %i[get post] : [:post]
|
||||
|
||||
@omniauth.call(env)
|
||||
rescue AuthenticatorDisabled => e
|
||||
@@ -71,5 +74,4 @@ class Middleware::OmniauthBypassMiddleware
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'method_profiler'
|
||||
require 'middleware/anonymous_cache'
|
||||
require "method_profiler"
|
||||
require "middleware/anonymous_cache"
|
||||
|
||||
class Middleware::RequestTracker
|
||||
@@detailed_request_loggers = nil
|
||||
@@ -15,7 +15,8 @@ class Middleware::RequestTracker
|
||||
# 14.15.16.32/27
|
||||
# 216.148.1.2
|
||||
#
|
||||
STATIC_IP_SKIPPER = ENV['DISCOURSE_MAX_REQS_PER_IP_EXCEPTIONS']&.split&.map { |ip| IPAddr.new(ip) }
|
||||
STATIC_IP_SKIPPER =
|
||||
ENV["DISCOURSE_MAX_REQS_PER_IP_EXCEPTIONS"]&.split&.map { |ip| IPAddr.new(ip) }
|
||||
|
||||
# register callbacks for detailed request loggers called on every request
|
||||
# example:
|
||||
@@ -30,9 +31,7 @@ class Middleware::RequestTracker
|
||||
|
||||
def self.unregister_detailed_request_logger(callback)
|
||||
@@detailed_request_loggers.delete(callback)
|
||||
if @@detailed_request_loggers.length == 0
|
||||
@detailed_request_loggers = nil
|
||||
end
|
||||
@detailed_request_loggers = nil if @@detailed_request_loggers.length == 0
|
||||
end
|
||||
|
||||
# used for testing
|
||||
@@ -107,7 +106,8 @@ class Middleware::RequestTracker
|
||||
env_track_view = env["HTTP_DISCOURSE_TRACK_VIEW"]
|
||||
track_view = status == 200
|
||||
track_view &&= env_track_view != "0" && env_track_view != "false"
|
||||
track_view &&= env_track_view || (request.get? && !request.xhr? && headers["Content-Type"] =~ /text\/html/)
|
||||
track_view &&=
|
||||
env_track_view || (request.get? && !request.xhr? && headers["Content-Type"] =~ %r{text/html})
|
||||
track_view = !!track_view
|
||||
has_auth_cookie = Auth::DefaultCurrentUserProvider.find_v0_auth_cookie(request).present?
|
||||
has_auth_cookie ||= Auth::DefaultCurrentUserProvider.find_v1_auth_cookie(env).present?
|
||||
@@ -128,7 +128,7 @@ class Middleware::RequestTracker
|
||||
is_mobile: helper.is_mobile?,
|
||||
track_view: track_view,
|
||||
timing: timing,
|
||||
queue_seconds: env['REQUEST_QUEUE_SECONDS']
|
||||
queue_seconds: env["REQUEST_QUEUE_SECONDS"],
|
||||
}
|
||||
|
||||
if h[:is_background]
|
||||
@@ -146,7 +146,7 @@ class Middleware::RequestTracker
|
||||
end
|
||||
|
||||
if h[:is_crawler]
|
||||
user_agent = env['HTTP_USER_AGENT']
|
||||
user_agent = env["HTTP_USER_AGENT"]
|
||||
if user_agent && (user_agent.encoding != Encoding::UTF_8)
|
||||
user_agent = user_agent.encode("utf-8")
|
||||
user_agent.scrub!
|
||||
@@ -163,7 +163,12 @@ class Middleware::RequestTracker
|
||||
|
||||
def log_request_info(env, result, info, request = nil)
|
||||
# we got to skip this on error ... its just logging
|
||||
data = self.class.get_data(env, result, info, request) rescue nil
|
||||
data =
|
||||
begin
|
||||
self.class.get_data(env, result, info, request)
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
|
||||
if data
|
||||
if result && (headers = result[1])
|
||||
@@ -179,15 +184,16 @@ class Middleware::RequestTracker
|
||||
end
|
||||
|
||||
def self.populate_request_queue_seconds!(env)
|
||||
if !env['REQUEST_QUEUE_SECONDS']
|
||||
if queue_start = env['HTTP_X_REQUEST_START']
|
||||
queue_start = if queue_start.start_with?("t=")
|
||||
queue_start.split("t=")[1].to_f
|
||||
else
|
||||
queue_start.to_f / 1000.0
|
||||
end
|
||||
if !env["REQUEST_QUEUE_SECONDS"]
|
||||
if queue_start = env["HTTP_X_REQUEST_START"]
|
||||
queue_start =
|
||||
if queue_start.start_with?("t=")
|
||||
queue_start.split("t=")[1].to_f
|
||||
else
|
||||
queue_start.to_f / 1000.0
|
||||
end
|
||||
queue_time = (Time.now.to_f - queue_start)
|
||||
env['REQUEST_QUEUE_SECONDS'] = queue_time
|
||||
env["REQUEST_QUEUE_SECONDS"] = queue_time
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -212,9 +218,9 @@ class Middleware::RequestTracker
|
||||
TEXT
|
||||
headers = {
|
||||
"Retry-After" => available_in.to_s,
|
||||
"Discourse-Rate-Limit-Error-Code" => error_code
|
||||
"Discourse-Rate-Limit-Error-Code" => error_code,
|
||||
}
|
||||
return [429, headers, [message]]
|
||||
return 429, headers, [message]
|
||||
end
|
||||
env["discourse.request_tracker"] = self
|
||||
|
||||
@@ -235,21 +241,21 @@ class Middleware::RequestTracker
|
||||
headers["X-Sql-Calls"] = sql[:calls].to_s
|
||||
headers["X-Sql-Time"] = "%0.6f" % sql[:duration]
|
||||
end
|
||||
if queue = env['REQUEST_QUEUE_SECONDS']
|
||||
if queue = env["REQUEST_QUEUE_SECONDS"]
|
||||
headers["X-Queue-Time"] = "%0.6f" % queue
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if env[Auth::DefaultCurrentUserProvider::BAD_TOKEN] && (headers = result[1])
|
||||
headers['Discourse-Logged-Out'] = '1'
|
||||
headers["Discourse-Logged-Out"] = "1"
|
||||
end
|
||||
|
||||
result
|
||||
ensure
|
||||
if (limiters = env['DISCOURSE_RATE_LIMITERS']) && env['DISCOURSE_IS_ASSET_PATH']
|
||||
if (limiters = env["DISCOURSE_RATE_LIMITERS"]) && env["DISCOURSE_IS_ASSET_PATH"]
|
||||
limiters.each(&:rollback!)
|
||||
env['DISCOURSE_ASSET_RATE_LIMITERS'].each do |limiter|
|
||||
env["DISCOURSE_ASSET_RATE_LIMITERS"].each do |limiter|
|
||||
begin
|
||||
limiter.performed!
|
||||
rescue RateLimiter::LimitExceeded
|
||||
@@ -257,25 +263,19 @@ class Middleware::RequestTracker
|
||||
end
|
||||
end
|
||||
end
|
||||
if !env["discourse.request_tracker.skip"]
|
||||
log_request_info(env, result, info, request)
|
||||
end
|
||||
log_request_info(env, result, info, request) if !env["discourse.request_tracker.skip"]
|
||||
end
|
||||
|
||||
def log_later(data)
|
||||
Scheduler::Defer.later("Track view") do
|
||||
unless Discourse.pg_readonly_mode?
|
||||
self.class.log_request(data)
|
||||
end
|
||||
self.class.log_request(data) unless Discourse.pg_readonly_mode?
|
||||
end
|
||||
end
|
||||
|
||||
def find_auth_cookie(env)
|
||||
min_allowed_timestamp = Time.now.to_i - (UserAuthToken::ROTATE_TIME_MINS + 1) * 60
|
||||
cookie = Auth::DefaultCurrentUserProvider.find_v1_auth_cookie(env)
|
||||
if cookie && cookie[:issued_at] >= min_allowed_timestamp
|
||||
cookie
|
||||
end
|
||||
cookie if cookie && cookie[:issued_at] >= min_allowed_timestamp
|
||||
end
|
||||
|
||||
def is_private_ip?(ip)
|
||||
@@ -286,10 +286,12 @@ class Middleware::RequestTracker
|
||||
end
|
||||
|
||||
def rate_limit(request, cookie)
|
||||
warn = GlobalSetting.max_reqs_per_ip_mode == "warn" ||
|
||||
GlobalSetting.max_reqs_per_ip_mode == "warn+block"
|
||||
block = GlobalSetting.max_reqs_per_ip_mode == "block" ||
|
||||
GlobalSetting.max_reqs_per_ip_mode == "warn+block"
|
||||
warn =
|
||||
GlobalSetting.max_reqs_per_ip_mode == "warn" ||
|
||||
GlobalSetting.max_reqs_per_ip_mode == "warn+block"
|
||||
block =
|
||||
GlobalSetting.max_reqs_per_ip_mode == "block" ||
|
||||
GlobalSetting.max_reqs_per_ip_mode == "warn+block"
|
||||
|
||||
return if !block && !warn
|
||||
|
||||
@@ -304,54 +306,56 @@ class Middleware::RequestTracker
|
||||
|
||||
ip_or_id = ip
|
||||
limit_on_id = false
|
||||
if cookie && cookie[:user_id] && cookie[:trust_level] && cookie[:trust_level] >= GlobalSetting.skip_per_ip_rate_limit_trust_level
|
||||
if cookie && cookie[:user_id] && cookie[:trust_level] &&
|
||||
cookie[:trust_level] >= GlobalSetting.skip_per_ip_rate_limit_trust_level
|
||||
ip_or_id = cookie[:user_id]
|
||||
limit_on_id = true
|
||||
end
|
||||
|
||||
limiter10 = RateLimiter.new(
|
||||
nil,
|
||||
"global_ip_limit_10_#{ip_or_id}",
|
||||
GlobalSetting.max_reqs_per_ip_per_10_seconds,
|
||||
10,
|
||||
global: !limit_on_id,
|
||||
aggressive: true,
|
||||
error_code: limit_on_id ? "id_10_secs_limit" : "ip_10_secs_limit"
|
||||
)
|
||||
limiter10 =
|
||||
RateLimiter.new(
|
||||
nil,
|
||||
"global_ip_limit_10_#{ip_or_id}",
|
||||
GlobalSetting.max_reqs_per_ip_per_10_seconds,
|
||||
10,
|
||||
global: !limit_on_id,
|
||||
aggressive: true,
|
||||
error_code: limit_on_id ? "id_10_secs_limit" : "ip_10_secs_limit",
|
||||
)
|
||||
|
||||
limiter60 = RateLimiter.new(
|
||||
nil,
|
||||
"global_ip_limit_60_#{ip_or_id}",
|
||||
GlobalSetting.max_reqs_per_ip_per_minute,
|
||||
60,
|
||||
global: !limit_on_id,
|
||||
error_code: limit_on_id ? "id_60_secs_limit" : "ip_60_secs_limit",
|
||||
aggressive: true
|
||||
)
|
||||
limiter60 =
|
||||
RateLimiter.new(
|
||||
nil,
|
||||
"global_ip_limit_60_#{ip_or_id}",
|
||||
GlobalSetting.max_reqs_per_ip_per_minute,
|
||||
60,
|
||||
global: !limit_on_id,
|
||||
error_code: limit_on_id ? "id_60_secs_limit" : "ip_60_secs_limit",
|
||||
aggressive: true,
|
||||
)
|
||||
|
||||
limiter_assets10 = RateLimiter.new(
|
||||
nil,
|
||||
"global_ip_limit_10_assets_#{ip_or_id}",
|
||||
GlobalSetting.max_asset_reqs_per_ip_per_10_seconds,
|
||||
10,
|
||||
error_code: limit_on_id ? "id_assets_10_secs_limit" : "ip_assets_10_secs_limit",
|
||||
global: !limit_on_id
|
||||
)
|
||||
limiter_assets10 =
|
||||
RateLimiter.new(
|
||||
nil,
|
||||
"global_ip_limit_10_assets_#{ip_or_id}",
|
||||
GlobalSetting.max_asset_reqs_per_ip_per_10_seconds,
|
||||
10,
|
||||
error_code: limit_on_id ? "id_assets_10_secs_limit" : "ip_assets_10_secs_limit",
|
||||
global: !limit_on_id,
|
||||
)
|
||||
|
||||
request.env['DISCOURSE_RATE_LIMITERS'] = [limiter10, limiter60]
|
||||
request.env['DISCOURSE_ASSET_RATE_LIMITERS'] = [limiter_assets10]
|
||||
request.env["DISCOURSE_RATE_LIMITERS"] = [limiter10, limiter60]
|
||||
request.env["DISCOURSE_ASSET_RATE_LIMITERS"] = [limiter_assets10]
|
||||
|
||||
if !limiter_assets10.can_perform?
|
||||
if warn
|
||||
Discourse.warn("Global asset IP rate limit exceeded for #{ip}: 10 second rate limit", uri: request.env["REQUEST_URI"])
|
||||
Discourse.warn(
|
||||
"Global asset IP rate limit exceeded for #{ip}: 10 second rate limit",
|
||||
uri: request.env["REQUEST_URI"],
|
||||
)
|
||||
end
|
||||
|
||||
if block
|
||||
return [
|
||||
limiter_assets10.seconds_to_wait(Time.now.to_i),
|
||||
limiter_assets10.error_code
|
||||
]
|
||||
end
|
||||
return limiter_assets10.seconds_to_wait(Time.now.to_i), limiter_assets10.error_code if block
|
||||
end
|
||||
|
||||
begin
|
||||
@@ -364,7 +368,10 @@ class Middleware::RequestTracker
|
||||
nil
|
||||
rescue RateLimiter::LimitExceeded => e
|
||||
if warn
|
||||
Discourse.warn("Global IP rate limit exceeded for #{ip}: #{type} second rate limit", uri: request.env["REQUEST_URI"])
|
||||
Discourse.warn(
|
||||
"Global IP rate limit exceeded for #{ip}: #{type} second rate limit",
|
||||
uri: request.env["REQUEST_URI"],
|
||||
)
|
||||
end
|
||||
if block
|
||||
[e.available_in, e.error_code]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
module Middleware
|
||||
|
||||
# Cheat and bypass Rails in development mode if the client attempts to download a static asset
|
||||
# that's already been downloaded.
|
||||
#
|
||||
@@ -19,22 +18,19 @@ module Middleware
|
||||
|
||||
def call(env)
|
||||
root = "#{GlobalSetting.relative_url_root}/assets/"
|
||||
is_asset = env['REQUEST_PATH'] && env['REQUEST_PATH'].starts_with?(root)
|
||||
is_asset = env["REQUEST_PATH"] && env["REQUEST_PATH"].starts_with?(root)
|
||||
|
||||
# hack to bypass all middleware if serving assets, a lot faster 4.5 seconds -> 1.5 seconds
|
||||
if (etag = env['HTTP_IF_NONE_MATCH']) && is_asset
|
||||
name = env['REQUEST_PATH'][(root.length)..-1]
|
||||
if (etag = env["HTTP_IF_NONE_MATCH"]) && is_asset
|
||||
name = env["REQUEST_PATH"][(root.length)..-1]
|
||||
etag = etag.gsub "\"", ""
|
||||
asset = Rails.application.assets.find_asset(name)
|
||||
if asset && asset.digest == etag
|
||||
return [304, {}, []]
|
||||
end
|
||||
return 304, {}, [] if asset && asset.digest == etag
|
||||
end
|
||||
|
||||
status, headers, response = @app.call(env)
|
||||
headers['Cache-Control'] = 'no-cache' if is_asset
|
||||
headers["Cache-Control"] = "no-cache" if is_asset
|
||||
[status, headers, response]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user