Project

General

Profile

Download (11.6 KB) Statistics
| Branch: | Tag: | Revision:
#--
# 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/>.
#++


$KCODE = 'UTF8'
require 'jcode'
require 'active_ldap'
require 'kwalify'
require 'ldap_shadows/activeldap_fixes'
require 'ldap_shadows/object'


module LdapShadows
class Controller
attr_reader :shadow_name, :shadow_config_path

def initialize(mod_container = LdapShadows::Objects)
@mod_container = mod_container

@schema = {}

clear_shadow
LdapShadows::LdapObject.mapper = self
end

def clear_shadow
@shadow_name = nil
@shadow_config_path = nil
@global_config = nil
@shadow_config = nil
@object_definitions = {}
@aspects = {}
# TODO: should replace @aspects properly one day
@aspects2 = {}
end

def set_shadow_config(shadow_config)
@shadow_config = shadow_config
end

def get_shadow_config
@shadow_config
end

class LdapShadowsValidator < Kwalify::Validator
## 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_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 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

def set_aspect(aspect_name, aspect_def)
schema_name = 'aspect'
schema = load_schema(schema_name)

# validate config with schema
validator = AspectValidator.new(schema)
parser = Kwalify::Yaml::Parser.new(validator)
aspect_def = parser.parse(aspect_def, aspect_name)
raise_if_validation_errors("#{schema_name.capitalize} '#{aspect_name}'", parser.errors)

aspect_name = aspect_name.to_sym
aspect_def.recursive_symbolize_keys!
@aspects[aspect_name] = aspect_def

return if @shadow_config_path.nil?
filename = File.join(@shadow_config_path, "hooks", "aspects", aspect_name.to_s.downcase + ".rb")
if File.exists?(filename)
klass_name = "LdapAspect" + aspect_name.to_s.capitalize
klass_content = IO.read(filename)
begin
Aspects.module_eval(<<-EOS)
class #{klass_name} < Aspect
#{klass_content}
end
EOS
rescue
raise PreProcessingError, _("Could not load Aspect plugin '%s'") % aspect_name
end

@aspects2[aspect_name] = Aspects.const_get(klass_name)
end
end

def get_aspect(aspect_name)
@aspects[aspect_name.to_sym]
end

def get_aspect_klass(aspect_name)
@aspects2[aspect_name.to_sym]
end

def self.object_name_to_klass_name(obj_name)
"LdapObject" + obj_name.to_s.capitalize
end

class ObjectValidator < LdapShadowsValidator
def validate_hook_in(value, rule, path, msg_list)
case rule.name
when 'Mapping'
ne_params = value.keys.collect{|k| k.to_sym } - ActiveLdap::Base::VALID_LDAP_MAPPING_OPTIONS
unless ne_params.empty?
msg_list << _("nonexisting mapping parameters (%s)") % ne_params.join(", ")
end

when 'RelationMapping'
validate_hook_relation(value, rule, path, msg_list)
end
end
end

def load_object(obj_name, obj_def)
schema_name = 'object'
schema = load_schema(schema_name)

# validate config with schema
validator = ObjectValidator.new(schema)
parser = Kwalify::Yaml::Parser.new(validator)
obj_def = parser.parse(obj_def, obj_name)
raise_if_validation_errors("#{schema_name.capitalize} '#{obj_name}'", parser.errors)

obj_name = obj_name.to_sym
obj_def.recursive_symbolize_keys!

klass_name = self.class.object_name_to_klass_name(obj_name)

# create class
@mod_container.module_eval(<<-EOS)
class #{klass_name} < LdapShadows::LdapObject; end
EOS

# configure class
klass = find_klass(obj_name)
klass.handle = obj_name
klass.presentation = obj_def[:presentation]
klass.mapper = self
klass.ldap_mapping obj_def[:mapping]

# store definition for later relations processing
@object_definitions[obj_name] = obj_def
end

def find_klass(obj_name)
klass_name = self.class.object_name_to_klass_name(obj_name)
return nil unless @mod_container.const_defined?(klass_name)
@mod_container.const_get(klass_name)
end

# run it _once_ when all objects are loaded
def load_relations
@object_definitions.each_pair do |obj_name, obj_def|
obj_rel = {}
obj_rel.merge!(obj_def[:relations]) if obj_def.include?(:relations)
if obj_def[:presentation].has_key?(:allowed_aspects)
obj_def[:presentation][:allowed_aspects].each do |aspect|
aspect_data = get_aspect(aspect)
if aspect_data.nil?
raise PreProcessingError, _("Aspect '%s' is missing") % aspect
end
obj_rel.merge!(aspect_data[:relations]) if aspect_data.has_key?(:relations) and aspect_data[:relations]
end
end
next if obj_rel.empty?

klass = find_klass(obj_name)

obj_relations_info = {}
obj_rel.each_pair do |field_name, rel|
foreign_klass = find_klass(rel[:object])
if foreign_klass.nil?
raise PreProcessingError, _("Relation '%s' for object '%s' is impossible: foreign object '%s' is missing") % [field_name, obj_name, rel[:object]]
end
rel[:class_name] = foreign_klass.to_s

case rel[:type]
when 'belongs_to'
klass.belongs_to field_name, rel.reject {|key, val| not ActiveLdap::Associations::ClassMethods::VALID_BELONGS_TO_OPTIONS.include?(key) }
when 'has_many'
klass.has_many field_name, rel.reject {|key, val| not ActiveLdap::Associations::ClassMethods::VALID_HAS_MANY_OPTIONS.include?(key) }
else
raise "bug in '#{obj_name}' object relations (wrong type)"
end

obj_relations_info[field_name] = {
:foreign_klass => foreign_klass,
:single_value => ActiveLdap::Base.schema.attribute(rel[:foreign_key]).single_value?,
:read_only => rel[:read_only] || false
}
end
klass.relations_info = obj_relations_info
end
end

def objects
@object_definitions.keys.collect{|key| key.to_s }.sort
end

def load_shadow(shadow_name = nil)
clear_shadow

g_config_file = File.join(LdapShadows::Config::CFG_DIR, "global.conf")
unless File.exists? g_config_file
raise PreProcessingError, _("Global LdapShadows config file is missing")
end

g_default_config = {}
g_config = g_default_config.merge(YAML.load(IO.read(g_config_file)) || {})
g_config.recursive_symbolize_keys!
@global_config = g_config

@shadow_name = shadow_name || @global_config[:default_shadow]
if @shadow_name.nil?
raise PreProcessingError, _("Could not determine which Shadow to travel through")
end

@shadow_config_path = File.join(LdapShadows::Config::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 = YAML.load(IO.read(config_file))
config_str_prv_filelist = [
File.join(ENV['HOME'], ".shadowwalker"),
File.join(@shadow_config_path, "shadow_private.conf")
]
config_str_prv_filelist.each do |file|
if File.exists?(file)
config_str_prv = IO.read(file)
config.merge!(YAML.load(config_str_prv))
break
end
end
config.recursive_symbolize_keys!

$ldapctl.set_shadow_config(config[:presentation])

load_config_components("aspects") do |c_name, c_config|
$ldapctl.set_aspect(c_name, c_config)
end

ActiveLdap::Base.setup_connection(config[:ldap])

load_config_components("objects") do |c_name, c_config|
$ldapctl.load_object(c_name, c_config)
end

$ldapctl.load_relations

translation_path = File.join($ldapctl.shadow_config_path, "translations")
if File.exists? translation_path
# load interface translation
I18n.load_path += Dir[File.join(translation_path, "**", "*.yml")]
end
rescue
raise PreProcessingError, _("Could not load shadow configuration: %s") % $!
end

protected

def load_config_components(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 = @schema[type]
if schema.nil?
schema_file = File.join(Config::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)

@schema[type] = schema
end
schema
rescue
raise PreProcessingError, _("Could not load schema: %s") % $!
end

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
end

# default location for mapped objects
module Objects
end

class Aspect
def self.hook_before_create
end

def self.hook_before_modify
end

protected

def self.raise_error(msg)
raise ProcessingError, msg
end
end

module Aspects
end
end

(2-2/7)