|
#--
|
|
# LdapShadows, a Medium-level LDAP Access Library and Tool.
|
|
# Copyright (c) 2009 Marc Dequènes (Duck) <Duck@DuckCorp.org>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#++
|
|
|
|
require 'ldap_shadows/config_setup'
|
|
require 'singleton'
|
|
require 'yaml'
|
|
require 'kwalify'
|
|
require 'facets/hash/recursive_merge'
|
|
|
|
module LdapShadows
|
|
class Config
|
|
include Singleton
|
|
|
|
def initialize
|
|
@global_config = nil
|
|
@schemas = {}
|
|
@default_configs = {}
|
|
@shadows = {}
|
|
end
|
|
|
|
def load_global_config(extra_private_config_list = [])
|
|
g_default_config = load_default_config('global')
|
|
|
|
g_config_file = File.join(CFG_DIR, "global.conf")
|
|
unless File.exists? g_config_file
|
|
raise PreProcessingError, _("Global LdapShadows config file is missing")
|
|
end
|
|
|
|
g_def_partial = YAML.load_file(g_config_file) || {}
|
|
extra_private_config_list += [
|
|
File.join(CFG_DIR, "global_private.conf")
|
|
]
|
|
extra_private_config_list.each do |file|
|
|
if File.exists?(file)
|
|
g_def_partial.merge!(YAML.load_file(file) || {})
|
|
break
|
|
end
|
|
end
|
|
g_def = parse('config', 'global', YAML.dump(g_def_partial))
|
|
g_config = g_default_config.recursive_merge(g_def)
|
|
|
|
ActiveLdap::Base.setup_connection(g_config[:ldap])
|
|
|
|
@global_config = g_config
|
|
end
|
|
|
|
def global_config
|
|
load_global_config() if @global_config.nil?
|
|
@global_config
|
|
end
|
|
|
|
# needs 'ldap_shadows/shadow' for this feature to work
|
|
def load_shadow(shadow_name = nil)
|
|
shadow_name = self.global_config[:default_shadow] if shadow_name.nil?
|
|
if shadow_name.nil?
|
|
raise PreProcessingError, _("Could not determine which Shadow to travel through")
|
|
end
|
|
|
|
shadow_config_path = File.join(CFG_DIR, "shadows", shadow_name)
|
|
unless File.exists? shadow_config_path
|
|
raise PreProcessingError, _("Configuration directory for Shadow '%s' is missing") % shadow_name
|
|
end
|
|
|
|
config_file = File.join(shadow_config_path, "shadow.conf")
|
|
unless File.exists? config_file
|
|
raise PreProcessingError, _("General configuration file for Shadow '%s' is missing") % shadow_name
|
|
end
|
|
config = File.read(config_file)
|
|
|
|
shadow = Shadow.new(shadow_name, self)
|
|
# register early (needed to load plugins)
|
|
@shadows[shadow_name] = {
|
|
:config_path => shadow_config_path,
|
|
:shadow => shadow
|
|
}
|
|
shadow.set_config(config)
|
|
|
|
load_element_config(shadow_config_path, 'aspects') do |c_name, c_config|
|
|
shadow.add_aspect(c_name, c_config)
|
|
end
|
|
|
|
load_element_config(shadow_config_path, 'objects') do |c_name, c_config|
|
|
shadow.add_object(c_name, c_config)
|
|
end
|
|
|
|
shadow.cast
|
|
|
|
# TODO: find a way to handle translations per-shadow
|
|
translation_path = File.join(shadow_config_path, "translations")
|
|
if File.exists? translation_path
|
|
# load interface translation
|
|
I18n.load_path += Dir[File.join(translation_path, "**", "*.yml")]
|
|
end
|
|
|
|
shadow
|
|
rescue
|
|
raise PreProcessingError, _("Could not load shadow configuration: %s") % $!
|
|
end
|
|
|
|
def get_shadow_info(shadow_name)
|
|
@shadows[shadow_name]
|
|
end
|
|
|
|
def parse(def_name, type, def_data_raw, validate = true, extra_data = nil)
|
|
schema = load_schema(type)
|
|
|
|
if validate
|
|
validator_klass = case type
|
|
when 'global'
|
|
GlobalValidator
|
|
when 'object'
|
|
ObjectValidator
|
|
when 'aspect'
|
|
AspectValidator
|
|
else
|
|
LdapShadowsValidator
|
|
end
|
|
validator = validator_klass.new(schema)
|
|
validator.extra_data = extra_data
|
|
else
|
|
validator = nil
|
|
end
|
|
|
|
# validate config with schema
|
|
parser = Kwalify::Yaml::Parser.new(validator)
|
|
def_data = parser.parse(def_data_raw, def_name)
|
|
raise_if_validation_errors("#{type.capitalize} '#{def_name}'", parser.errors)
|
|
|
|
def_data.recursive_symbolize_keys!
|
|
end
|
|
|
|
def load_default_config(type)
|
|
default_config = @default_configs[type]
|
|
if default_config.nil?
|
|
filename = File.join(DATA_DIR, "default_config", type + ".yaml")
|
|
default_config_raw = File.read(filename)
|
|
default_config = parse(':defaults:', type, default_config_raw, false)
|
|
|
|
@default_configs[type] = default_config
|
|
end
|
|
default_config
|
|
rescue
|
|
raise PreProcessingError, _("Could not load default config for '%s': %s") % [type, $!]
|
|
end
|
|
|
|
def load_hook_content(shadow_name, type, hook_name)
|
|
s_info = get_shadow_info(shadow_name)
|
|
return nil if s_info.nil?
|
|
|
|
filename = File.join(s_info[:config_path], "plugins", type.pluralize, hook_name.to_s.downcase + ".rb")
|
|
return nil unless File.exists?(filename)
|
|
|
|
File.read(filename)
|
|
end
|
|
|
|
def load_element_config(shadow_config_path, type)
|
|
c_config_dir = File.join(shadow_config_path, type)
|
|
c_config_pattern = File.join(c_config_dir, "**", "*.conf")
|
|
|
|
Dir.glob(c_config_pattern).each do |f|
|
|
next if f[0..0] == "."
|
|
|
|
c_name = File.basename(f).sub(".conf", "")
|
|
c_config = File.read(f)
|
|
|
|
yield(c_name, c_config)
|
|
end
|
|
end
|
|
|
|
def load_schema(type)
|
|
schema = @schemas[type]
|
|
if schema.nil?
|
|
schema_file = File.join(DATA_DIR, "schema", type + ".yaml")
|
|
schema = YAML.load_file(schema_file)
|
|
|
|
# validate schema
|
|
metavalidator = Kwalify::MetaValidator.instance
|
|
errors = metavalidator.validate(schema)
|
|
raise_if_validation_errors("'#{type}' schema", errors)
|
|
|
|
@schemas[type] = schema
|
|
end
|
|
schema
|
|
rescue
|
|
raise PreProcessingError, _("Could not load schema for '%s': %s") % [type, $!]
|
|
end
|
|
|
|
protected
|
|
|
|
def raise_if_validation_errors(name, errors)
|
|
if errors and not errors.empty?
|
|
err_msg = []
|
|
for e in errors
|
|
err_msg << "[#{e.path}] #{e.message}"
|
|
end
|
|
raise PreProcessingError, _("%s is not valid:\n%s") % [name, err_msg.join("\n")]
|
|
end
|
|
end
|
|
|
|
class LdapShadowsValidator < Kwalify::Validator
|
|
attr_accessor :extra_data
|
|
|
|
## hook method called by Validator#validate()
|
|
def validate_hook(value, rule, path, errors)
|
|
msg_list = []
|
|
|
|
validate_hook_in(value, rule, path, msg_list)
|
|
|
|
msg_list.each do |msg|
|
|
errors << Kwalify::ValidationError.new(msg, path)
|
|
end
|
|
end
|
|
|
|
def validate_hook_in(value, rule, path, msg_list)
|
|
end
|
|
|
|
def validate_hook_relation(value, rule, path, msg_list)
|
|
extra_params = [:type, :object]
|
|
|
|
if value['type'] == 'belongs_to'
|
|
ne_params = value.keys.collect{|k| k.to_sym } -
|
|
ActiveLdap::Associations::ClassMethods::VALID_BELONGS_TO_OPTIONS - extra_params
|
|
unless ne_params.empty?
|
|
msg_list << _("nonexisting relation mapping parameters (%s)") % ne_params.join(", ")
|
|
end
|
|
else
|
|
ne_params = value.keys.collect{|k| k.to_sym } -
|
|
ActiveLdap::Associations::ClassMethods::VALID_HAS_MANY_OPTIONS - extra_params
|
|
unless ne_params.empty?
|
|
msg_list << _("nonexisting relation mapping parameters (%s)") % ne_params.join(", ")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
class GlobalValidator < LdapShadowsValidator
|
|
def validate_hook_in(value, rule, path, msg_list)
|
|
case rule.name
|
|
when 'LdapMapping'
|
|
ne_params = value.keys.collect{|k| k.to_sym } -
|
|
ActiveLdap::Configuration::ClassMethods::CONNECTION_CONFIGURATION_KEYS -
|
|
ActiveLdap::Adapter::Base::VALID_ADAPTER_CONFIGURATION_KEYS
|
|
unless ne_params.empty?
|
|
msg_list << _("nonexisting LDAP mapping parameters (%s)") % ne_params.join(", ")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
class ObjectValidator < LdapShadowsValidator
|
|
def validate_hook_in(value, rule, path, msg_list)
|
|
case rule.name
|
|
when 'ObjectMapping'
|
|
ne_params = value.keys.collect{|k| k.to_sym } -
|
|
ActiveLdap::Base::VALID_LDAP_MAPPING_OPTIONS -
|
|
@extra_data[:default_parameters][:mapping].keys
|
|
unless ne_params.empty?
|
|
msg_list << _("nonexisting object mapping parameters (%s)") % ne_params.join(", ")
|
|
end
|
|
|
|
when 'RelationMapping'
|
|
validate_hook_relation(value, rule, path, msg_list)
|
|
end
|
|
end
|
|
end
|
|
|
|
class AspectValidator < LdapShadowsValidator
|
|
def validate_hook_in(value, rule, path, msg_list)
|
|
case rule.name
|
|
when 'RelationMapping'
|
|
validate_hook_relation(value, rule, path, msg_list)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|