Project

General

Profile

Download (8.98 KB) Statistics
| Branch: | Tag: | Revision:
require 'resolv'

class Setting < ApplicationRecord
audited :except => [:name]
extend FriendlyId
friendly_id :name
include ActiveModel::Validations
include EncryptValue
include PermissionName

TYPES = %w{integer boolean hash array string}
NONZERO_ATTRS = %w{puppet_interval idle_timeout entries_per_page outofsync_interval}
# constant BLANK_ATTRS is deprecated and all settings without custom validation allow blank values
# if you wish to validate non-empty arrays, please add validation through the new setting DSL
BLANK_ATTRS = %w{}
ARRAY_HOSTNAMES = %w{trusted_hosts}
URI_ATTRS = %w{foreman_url unattended_url}
URI_BLANK_ATTRS = %w{login_delegation_logout_url}
IP_ATTRS = %w{libvirt_default_console_address}
REGEXP_ATTRS = %w{}
EMAIL_ATTRS = %w{administrator email_reply_address}
NOT_STRIPPED = %w{}

class ValueValidator < ActiveModel::Validator
def validate(record)
record.send("validate_#{record.name}", record)
end
end

validates_lengths_from_database

validates :name, :presence => true, :uniqueness => true
validates :value, :numericality => true, :length => {:maximum => 8}, :if => proc { |s| s.settings_type == "integer" }
validates :value, :numericality => {:greater_than => 0}, :if => proc { |s| NONZERO_ATTRS.include?(s.name) }
validates :value, :inclusion => {:in => [true, false]}, :if => proc { |s| s.settings_type.to_s == "boolean" }, :allow_nil => true
validates :value, :url_schema => ['http', 'https'], :if => proc { |s| URI_ATTRS.include?(s.name) }

validates :value, :url_schema => ['http', 'https'], :if => proc { |s| URI_BLANK_ATTRS.include?(s.name) && s.value.present? }

validate :validate_host_owner, :if => proc { |s| s.name == "host_owner" }
validates :value, :format => { :with => Resolv::AddressRegex }, :if => proc { |s| IP_ATTRS.include? s.name }
validates :value, :regexp => true, :if => proc { |s| REGEXP_ATTRS.include? s.name }
validates :value, :array_type => true, :if => proc { |s| s.settings_type == "array" }
validates_with ValueValidator, :if => proc { |s| Foreman.settings.ready? && s.respond_to?("validate_#{s.name}") }
validates :value, :array_hostnames_ips => true, :if => proc { |s| ARRAY_HOSTNAMES.include? s.name }
validates :value, :email => true, :if => proc { |s| EMAIL_ATTRS.include? s.name }
before_save :clear_value_when_default
validate :validate_frozen_attributes
before_validation :remove_whitespaces, :if => proc { |s| s.settings_type == "array" }
# Custom validations are added from SettingManager class
after_find :readonly_when_overridden
after_save :refresh_registry_value
default_scope -> { order(:name) }

scope :order_by, ->(attr) { except(:order).order(attr) }

scoped_search :on => :id, :complete_enabled => false, :only_explicit => true, :validator => ScopedSearch::Validators::INTEGER
scoped_search on: :name, complete_value: :true, operators: ['=', '~']
scoped_search on: :description, complete_value: :true, operators: ['~']

delegate :settings_type, :encrypted, :encrypted?, :default, to: :setting_definition, allow_nil: true

def self.config_file
'settings.yaml'
end

# can't use our own settings
def self.per_page
20
end

def self.complete_for(search_query, opts = {})
SettingRegistry::SettingCompleter.auto_complete(Foreman.settings, scoped_search_definition, search_query, opts)
end

def self.[](name)
Foreman.settings[name]
end

def self.[]=(name, value)
Foreman.settings[name] = value
end

def self.setting_type_from_value(value_for_type)
t = value_for_type.class.to_s.downcase
return t if TYPES.include?(t)
return "integer" if value_for_type.is_a?(Integer)
return "boolean" if value_for_type.is_a?(TrueClass) || value_for_type.is_a?(FalseClass)
end

def to_param
name
end

def value=(v)
v = v.to_yaml unless v.nil?
# the has_attribute is for enabling DB migrations on older versions
if setting_definition&.encrypted?
# Don't re-write the attribute if the current encrypted value is identical to the new one
current_value = self[:value]
unless is_decryptable?(current_value) && decrypt_field(current_value) == v
self[:value] = encrypt_field(v)
end
else
self[:value] = v
end
end

def value
v = self[:value]
v = decrypt_field(v)
v.nil? ? default : YAML.safe_load(v, permitted_classes: [Symbol, Pathname])
end
alias_method :value_before_type_cast, :value

def parse_string_value(val)
case settings_type
when "boolean"
boolean = Foreman::Cast.to_bool(val)

if boolean.nil?
invalid_value_error _("must be boolean")
end

self.value = boolean

when "integer"
if val.to_s =~ /\A\d+\Z/
self.value = val.to_i
else
invalid_value_error _("must be integer")
end

when "array"
if val =~ /\A\[.*\]\Z/
begin
self.value = YAML.safe_load(val.gsub(/(\,)(\S)/, "\\1 \\2"))
rescue => e
invalid_value_error e.to_s
end
else
invalid_value_error _("must be an array")
end

when "string", "text", nil
# string is taken as default setting type for parsing
self.value = NOT_STRIPPED.include?(name) ? val : val.to_s.strip

when "hash"
raise Foreman::SettingValueException, N_("Parsing a hash from a string is not supported")

else
raise Foreman::SettingValueException.new(N_("Parsing settings type '%s' from a string is not defined"), settings_type)

end
if errors.present?
raise Foreman::SettingValueException.new(N_("Error parsing value for setting '%(name)s': %(error)s"),
{ 'name' => name, 'error' => errors.full_messages.join(", ") })
end
true
end

def self.regexp_expand_wildcard_string(string, options = {})
prefix = options[:prefix] || '\A'
suffix = options[:suffix] || '\Z'
prefix + Regexp.escape(string).gsub('\*', '.*').gsub('\?', '.') + suffix
end

def self.convert_array_to_regexp(array, regexp_options = {})
Regexp.new(array.map { |string| regexp_expand_wildcard_string(string, regexp_options) }.join('|'))
end

def has_readonly_value?
SETTINGS.key?(name.to_sym)
end

def self.readonly_value(name)
SETTINGS[name]
end

def read_attribute_before_type_cast(attr_name)
return value if attr_name == :value
super(attr_name)
end

def self.replace_keywords(keyword)
keyword&.gsub '$VERSION', SETTINGS[:version].version
end

# Methods for loading default settings

def self.default_settings
[]
end

def self.load_defaults
return false unless table_exists?
Foreman::Deprecation.deprecation_warning('3.4', "subclassing Setting is deprecated '#{name}' should be migrated to setting DSL "\
'see https://github.com/theforeman/foreman/blob/develop/developer_docs/how_to_create_a_plugin.asciidoc#settings for details')
default_settings.each do |s|
t = Setting.setting_type_from_value(s[:default]) || 'string'
kwargs = s.except(:name).merge(type: t.to_sym, category: name.delete_prefix('Setting::'), context: :deprecated)
Foreman.settings._add(s[:name], **kwargs)
end
true
end

def self.select_collection_registry
Foreman.settings.select_collection_registry
end

def self.set(name, description, default, full_name = nil, value = nil, options = {})
if options.has_key? :collection
select_collection_registry.add(name, options)
end
options[:encrypted] ||= false
{:name => name, :value => value, :description => description, :default => default, :full_name => full_name, :encrypted => options[:encrypted]}
end

def select_collection
self.class.select_collection_registry.collection_for self
end

def self.model_name
ActiveModel::Name.new(Setting)
end

# End methods for loading default settings

private

def validate_host_owner
owner_type_and_id = value
return if owner_type_and_id.blank?

OwnerClassifier.classify_owner(owner_type_and_id)
rescue ArgumentError, ActiveRecord::RecordNotFound => e
errors.add(:value, e.message)
end

def invalid_value_error(error)
errors.add(:value, _("is invalid: %s") % error)
end

def validate_frozen_attributes
return true if new_record?
changed_attributes.each do |c, old|
# Allow settings_type to change at first (from nil) since it gets populated during validation
if c.to_s == 'name'
errors.add(c, _("is not allowed to change"))
return false
end
end
true
end

def clear_value_when_default
if value == default
self[:value] = nil
end
end

def readonly_when_overridden
readonly! if !new_record? && has_readonly_value?
end

def setting_definition
return unless Foreman.settings.ready?
Foreman.settings.find(name)
end

def refresh_registry_value
setting_definition&.tap do |definition|
definition.updated_at = updated_at
definition.value_from_db = value
end
end

def remove_whitespaces
self[:value] = value.each { |a| a.strip! if a.respond_to? :strip! }
end
end
(47-47/69)