2019-05-02 17:17:27 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2013-12-19 22:12:23 -06:00
|
|
|
class GlobalSetting
|
|
|
|
|
2014-01-01 17:46:09 -06:00
|
|
|
def self.register(key, default)
|
|
|
|
define_singleton_method(key) do
|
|
|
|
provider.lookup(key, default)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-06-22 05:55:58 -05:00
|
|
|
VALID_SECRET_KEY ||= /^[0-9a-f]{128}$/
|
2017-01-31 16:21:37 -06:00
|
|
|
# this is named SECRET_TOKEN as opposed to SECRET_KEY_BASE
|
|
|
|
# for legacy reasons
|
2017-06-22 05:55:58 -05:00
|
|
|
REDIS_SECRET_KEY ||= 'SECRET_TOKEN'
|
2017-01-31 16:21:37 -06:00
|
|
|
|
2017-06-22 05:55:58 -05:00
|
|
|
REDIS_VALIDATE_SECONDS ||= 30
|
2017-03-13 09:19:02 -05:00
|
|
|
|
2017-01-31 16:21:37 -06:00
|
|
|
# In Rails secret_key_base is used to encrypt the cookie store
|
|
|
|
# the cookie store contains session data
|
|
|
|
# Discourse also uses this secret key to digest user auth tokens
|
|
|
|
# This method will
|
|
|
|
# - use existing token if already set in ENV or discourse.conf
|
|
|
|
# - generate a token on the fly if needed and cache in redis
|
|
|
|
# - enforce rules about token format falling back to redis if needed
|
|
|
|
def self.safe_secret_key_base
|
2017-03-13 09:19:02 -05:00
|
|
|
|
|
|
|
if @safe_secret_key_base && @token_in_redis && (@token_last_validated + REDIS_VALIDATE_SECONDS) < Time.now
|
2017-03-13 09:47:43 -05:00
|
|
|
@token_last_validated = Time.now
|
2019-12-03 03:05:53 -06:00
|
|
|
token = Discourse.redis.without_namespace.get(REDIS_SECRET_KEY)
|
2017-03-13 09:19:02 -05:00
|
|
|
if token.nil?
|
2019-12-03 03:05:53 -06:00
|
|
|
Discourse.redis.without_namespace.set(REDIS_SECRET_KEY, @safe_secret_key_base)
|
2017-03-13 09:19:02 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-01-31 16:21:37 -06:00
|
|
|
@safe_secret_key_base ||= begin
|
|
|
|
token = secret_key_base
|
|
|
|
if token.blank? || token !~ VALID_SECRET_KEY
|
2017-03-13 09:19:02 -05:00
|
|
|
|
|
|
|
@token_in_redis = true
|
|
|
|
@token_last_validated = Time.now
|
|
|
|
|
2019-12-03 03:05:53 -06:00
|
|
|
token = Discourse.redis.without_namespace.get(REDIS_SECRET_KEY)
|
2017-01-31 16:21:37 -06:00
|
|
|
unless token && token =~ VALID_SECRET_KEY
|
|
|
|
token = SecureRandom.hex(64)
|
2019-12-03 03:05:53 -06:00
|
|
|
Discourse.redis.without_namespace.set(REDIS_SECRET_KEY, token)
|
2017-01-31 16:21:37 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
if !secret_key_base.blank? && token != secret_key_base
|
|
|
|
STDERR.puts "WARNING: DISCOURSE_SECRET_KEY_BASE is invalid, it was re-generated"
|
|
|
|
end
|
|
|
|
token
|
|
|
|
end
|
2017-08-02 00:32:01 -05:00
|
|
|
rescue Redis::CommandError => e
|
|
|
|
@safe_secret_key_base = SecureRandom.hex(64) if e.message =~ /READONLY/
|
2017-01-31 16:21:37 -06:00
|
|
|
end
|
|
|
|
|
2013-12-19 23:38:51 -06:00
|
|
|
def self.load_defaults
|
2013-12-20 00:41:12 -06:00
|
|
|
default_provider = FileProvider.from(File.expand_path('../../../config/discourse_defaults.conf', __FILE__))
|
2014-01-01 17:46:09 -06:00
|
|
|
default_provider.keys.concat(@provider.keys).uniq.each do |key|
|
|
|
|
default = default_provider.lookup(key, nil)
|
2017-03-09 17:00:55 -06:00
|
|
|
|
|
|
|
instance_variable_set("@#{key}_cache", nil)
|
|
|
|
|
2014-01-01 17:46:09 -06:00
|
|
|
define_singleton_method(key) do
|
2017-03-09 17:00:55 -06:00
|
|
|
val = instance_variable_get("@#{key}_cache")
|
|
|
|
unless val.nil?
|
|
|
|
val == :missing ? nil : val
|
|
|
|
else
|
|
|
|
val = provider.lookup(key, default)
|
|
|
|
if val.nil?
|
|
|
|
val = :missing
|
|
|
|
end
|
|
|
|
instance_variable_set("@#{key}_cache", val)
|
|
|
|
val == :missing ? nil : val
|
|
|
|
end
|
2013-12-19 22:12:23 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-06-12 21:58:27 -05:00
|
|
|
def self.skip_db=(v)
|
|
|
|
@skip_db = v
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.skip_db?
|
|
|
|
@skip_db
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.skip_redis=(v)
|
|
|
|
@skip_redis = v
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.skip_redis?
|
|
|
|
@skip_redis
|
|
|
|
end
|
|
|
|
|
2017-10-06 00:20:01 -05:00
|
|
|
def self.use_s3?
|
|
|
|
(@use_s3 ||=
|
|
|
|
begin
|
|
|
|
s3_bucket &&
|
|
|
|
s3_region && (
|
|
|
|
s3_use_iam_profile || (s3_access_key_id && s3_secret_access_key)
|
|
|
|
) ? :true : :false
|
|
|
|
end) == :true
|
|
|
|
end
|
|
|
|
|
2018-05-16 15:10:15 -05:00
|
|
|
def self.s3_bucket_name
|
|
|
|
@s3_bucket_name ||= s3_bucket.downcase.split("/")[0]
|
|
|
|
end
|
|
|
|
|
2017-10-06 00:20:01 -05:00
|
|
|
# for testing
|
|
|
|
def self.reset_s3_cache!
|
|
|
|
@use_s3 = nil
|
|
|
|
end
|
|
|
|
|
2014-01-13 23:59:55 -06:00
|
|
|
def self.database_config
|
2017-07-27 20:20:09 -05:00
|
|
|
hash = { "adapter" => "postgresql" }
|
2018-03-08 20:22:29 -06:00
|
|
|
|
|
|
|
%w{
|
|
|
|
pool
|
|
|
|
connect_timeout
|
|
|
|
timeout
|
|
|
|
socket
|
|
|
|
host
|
|
|
|
backup_host
|
|
|
|
port
|
|
|
|
backup_port
|
|
|
|
username
|
|
|
|
password
|
|
|
|
replica_host
|
|
|
|
replica_port
|
|
|
|
}.each do |s|
|
2019-05-06 20:27:05 -05:00
|
|
|
if val = self.public_send("db_#{s}")
|
2014-01-13 23:59:55 -06:00
|
|
|
hash[s] = val
|
|
|
|
end
|
|
|
|
end
|
2016-01-25 00:27:59 -06:00
|
|
|
|
|
|
|
hash["adapter"] = "postgresql_fallback" if hash["replica_host"]
|
|
|
|
|
2015-07-23 00:22:54 -05:00
|
|
|
hostnames = [ hostname ]
|
2015-07-23 00:33:38 -05:00
|
|
|
hostnames << backup_hostname if backup_hostname.present?
|
2015-07-23 00:22:54 -05:00
|
|
|
|
2018-03-27 14:20:22 -05:00
|
|
|
hostnames << URI.parse(cdn_url).host if cdn_url.present?
|
2020-05-12 10:34:12 -05:00
|
|
|
hostnames << cdn_origin_hostname if cdn_origin_hostname.present?
|
2018-03-27 14:20:22 -05:00
|
|
|
|
2015-07-23 00:22:54 -05:00
|
|
|
hash["host_names"] = hostnames
|
2014-01-13 23:59:55 -06:00
|
|
|
hash["database"] = db_name
|
|
|
|
|
2015-02-17 18:16:53 -06:00
|
|
|
hash["prepared_statements"] = !!self.db_prepared_statements
|
|
|
|
|
2017-07-27 20:20:09 -05:00
|
|
|
{ "production" => hash }
|
2014-01-13 23:59:55 -06:00
|
|
|
end
|
|
|
|
|
2017-02-01 23:47:57 -06:00
|
|
|
# For testing purposes
|
|
|
|
def self.reset_redis_config!
|
|
|
|
@config = nil
|
2019-05-28 00:52:43 -05:00
|
|
|
@message_bus_config = nil
|
2017-02-01 23:47:57 -06:00
|
|
|
end
|
|
|
|
|
2015-06-25 01:51:48 -05:00
|
|
|
def self.redis_config
|
|
|
|
@config ||=
|
|
|
|
begin
|
|
|
|
c = {}
|
|
|
|
c[:host] = redis_host if redis_host
|
|
|
|
c[:port] = redis_port if redis_port
|
2017-02-01 23:47:57 -06:00
|
|
|
|
|
|
|
if redis_slave_host && redis_slave_port
|
2020-05-22 23:56:13 -05:00
|
|
|
if ENV["RAILS_FAILOVER"]
|
|
|
|
c[:replica_host] = redis_slave_host
|
|
|
|
c[:replica_port] = redis_slave_port
|
|
|
|
c[:connector] = RailsFailover::Redis::Connector
|
|
|
|
else
|
|
|
|
c[:slave_host] = redis_slave_host
|
|
|
|
c[:slave_port] = redis_slave_port
|
|
|
|
c[:connector] = DiscourseRedis::Connector
|
|
|
|
end
|
2017-02-01 23:47:57 -06:00
|
|
|
end
|
|
|
|
|
2015-06-30 21:19:02 -05:00
|
|
|
c[:password] = redis_password if redis_password.present?
|
2015-06-25 01:51:48 -05:00
|
|
|
c[:db] = redis_db if redis_db != 0
|
|
|
|
c[:db] = 1 if Rails.env == "test"
|
2019-01-03 22:08:22 -06:00
|
|
|
c[:id] = nil if redis_skip_client_commands
|
2020-03-05 16:21:38 -06:00
|
|
|
c[:ssl] = true if redis_use_ssl
|
2017-02-01 23:47:57 -06:00
|
|
|
|
2015-06-25 01:51:48 -05:00
|
|
|
c.freeze
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-05-28 00:52:43 -05:00
|
|
|
def self.message_bus_redis_config
|
|
|
|
return redis_config unless message_bus_redis_enabled
|
|
|
|
@message_bus_config ||=
|
|
|
|
begin
|
|
|
|
c = {}
|
|
|
|
c[:host] = message_bus_redis_host if message_bus_redis_host
|
|
|
|
c[:port] = message_bus_redis_port if message_bus_redis_port
|
|
|
|
|
|
|
|
if message_bus_redis_slave_host && message_bus_redis_slave_port
|
2020-05-22 23:56:13 -05:00
|
|
|
if ENV["RAILS_FAILOVER"]
|
|
|
|
c[:replica_host] = message_bus_redis_slave_host
|
|
|
|
c[:replica_port] = message_bus_redis_slave_port
|
|
|
|
c[:connector] = RailsFailover::Redis::Connector
|
|
|
|
else
|
|
|
|
c[:slave_host] = message_bus_redis_slave_host
|
|
|
|
c[:slave_port] = message_bus_redis_slave_port
|
|
|
|
c[:connector] = DiscourseRedis::Connector
|
|
|
|
end
|
2019-05-28 00:52:43 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
c[:password] = message_bus_redis_password if message_bus_redis_password.present?
|
|
|
|
c[:db] = message_bus_redis_db if message_bus_redis_db != 0
|
|
|
|
c[:db] = 1 if Rails.env == "test"
|
|
|
|
c[:id] = nil if message_bus_redis_skip_client_commands
|
2020-03-05 16:21:38 -06:00
|
|
|
c[:ssl] = true if redis_use_ssl
|
2019-05-28 00:52:43 -05:00
|
|
|
|
|
|
|
c.freeze
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-11-15 15:38:24 -06:00
|
|
|
def self.add_default(name, default)
|
|
|
|
unless self.respond_to? name
|
|
|
|
define_singleton_method(name) do
|
|
|
|
default
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-12-19 22:12:23 -06:00
|
|
|
class BaseProvider
|
2013-12-19 23:17:21 -06:00
|
|
|
def self.coerce(setting)
|
|
|
|
return setting == "true" if setting == "true" || setting == "false"
|
|
|
|
return $1.to_i if setting.to_s.strip =~ /^([0-9]+)$/
|
|
|
|
setting
|
|
|
|
end
|
|
|
|
|
|
|
|
def resolve(current, default)
|
|
|
|
BaseProvider.coerce(
|
|
|
|
if current.present?
|
|
|
|
current
|
|
|
|
else
|
|
|
|
default.present? ? default : nil
|
|
|
|
end
|
|
|
|
)
|
2013-12-19 22:12:23 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-12-19 23:17:21 -06:00
|
|
|
class FileProvider < BaseProvider
|
2013-12-19 23:38:51 -06:00
|
|
|
attr_reader :data
|
2013-12-19 23:17:21 -06:00
|
|
|
def self.from(file)
|
|
|
|
if File.exists?(file)
|
|
|
|
parse(file)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def initialize(file)
|
|
|
|
@file = file
|
|
|
|
@data = {}
|
|
|
|
end
|
|
|
|
|
|
|
|
def read
|
2014-02-14 15:35:30 -06:00
|
|
|
ERB.new(File.read(@file)).result().split("\n").each do |line|
|
2015-08-14 02:01:06 -05:00
|
|
|
if line =~ /^\s*([a-z_]+[a-z0-9_]*)\s*=\s*(\"([^\"]*)\"|\'([^\']*)\'|[^#]*)/
|
2013-12-19 23:17:21 -06:00
|
|
|
@data[$1.strip.to_sym] = ($4 || $3 || $2).strip
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-07-27 20:20:09 -05:00
|
|
|
def lookup(key, default)
|
2013-12-20 00:09:51 -06:00
|
|
|
var = @data[key]
|
|
|
|
resolve(var, var.nil? ? default : "")
|
2013-12-19 23:17:21 -06:00
|
|
|
end
|
|
|
|
|
2014-01-01 17:46:09 -06:00
|
|
|
def keys
|
|
|
|
@data.keys
|
|
|
|
end
|
|
|
|
|
2013-12-19 23:17:21 -06:00
|
|
|
def self.parse(file)
|
|
|
|
provider = self.new(file)
|
|
|
|
provider.read
|
|
|
|
provider
|
2013-12-19 22:12:23 -06:00
|
|
|
end
|
2014-10-03 22:07:20 -05:00
|
|
|
|
|
|
|
private_class_method :parse
|
2013-12-19 22:12:23 -06:00
|
|
|
end
|
|
|
|
|
2013-12-19 23:17:21 -06:00
|
|
|
class EnvProvider < BaseProvider
|
|
|
|
def lookup(key, default)
|
2019-05-02 17:17:27 -05:00
|
|
|
var = ENV["DISCOURSE_" + key.to_s.upcase]
|
2013-12-20 00:07:08 -06:00
|
|
|
resolve(var , var.nil? ? default : nil)
|
2013-12-19 23:17:21 -06:00
|
|
|
end
|
2014-01-01 17:46:09 -06:00
|
|
|
|
|
|
|
def keys
|
2017-07-27 20:20:09 -05:00
|
|
|
ENV.keys.select { |k| k =~ /^DISCOURSE_/ }.map { |k| k[10..-1].downcase.to_sym }
|
2014-01-01 17:46:09 -06:00
|
|
|
end
|
2013-12-19 22:12:23 -06:00
|
|
|
end
|
|
|
|
|
2015-03-08 20:21:41 -05:00
|
|
|
class BlankProvider < BaseProvider
|
|
|
|
def lookup(key, default)
|
2019-04-01 03:27:49 -05:00
|
|
|
|
|
|
|
if key == :redis_port
|
|
|
|
return ENV["DISCOURSE_REDIS_PORT"] if ENV["DISCOURSE_REDIS_PORT"]
|
|
|
|
end
|
2015-03-08 20:21:41 -05:00
|
|
|
default
|
|
|
|
end
|
|
|
|
|
|
|
|
def keys
|
|
|
|
[]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-12-19 22:12:23 -06:00
|
|
|
class << self
|
|
|
|
attr_accessor :provider
|
|
|
|
end
|
|
|
|
|
2017-01-09 16:10:14 -06:00
|
|
|
def self.configure!
|
|
|
|
if Rails.env == "test"
|
|
|
|
@provider = BlankProvider.new
|
|
|
|
else
|
|
|
|
@provider =
|
|
|
|
FileProvider.from(File.expand_path('../../../config/discourse.conf', __FILE__)) ||
|
|
|
|
EnvProvider.new
|
|
|
|
end
|
2015-03-08 20:21:41 -05:00
|
|
|
end
|
2014-01-01 17:46:09 -06:00
|
|
|
|
2013-12-19 22:12:23 -06:00
|
|
|
end
|