root/lib/ldap_shadows/manipulation_helper.rb @ 4b490148
2bd92292 | Marc Dequènes (Duck) | #--
|
|
# LdapShadows, a Medium-level LDAP Access Library and Tool.
|
|||
bc2c2691 | Marc Dequènes (Duck) | # Copyright (c) 2009-2010 Marc Dequènes (Duck) <Duck@DuckCorp.org>
|
|
2bd92292 | Marc Dequènes (Duck) | #
|
|
# 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/>.
|
|||
#++
|
|||
module LdapShadows
|
|||
module Manipulation
|
|||
016427dd | Marc Dequènes (Duck) | # hyphens are permitted by RFC2251, but disallowed for method names
|
|
29460ef7 | Marc Dequènes (Duck) | TEXTUI_KEY_PATTERN = "(?:[a-zA-Z]*:)?[a-zA-Z][a-zA-Z0-9]*"
|
|
016427dd | Marc Dequènes (Duck) | def self.prepare_string_part(key, op_modifier, op, val)
|
|
case op_modifier
|
|||
when '@'
|
|||
unless File.exists?(val)
|
|||
raise PreProcessingError, _("The file for key '%s' does not exist") % key
|
|||
end
|
|||
begin
|
|||
val = File.read(val)
|
|||
rescue
|
|||
raise PreProcessingError, _("The file for key '%s' cannot be read: ") % [key, $!]
|
|||
end
|
|||
end
|
|||
val
|
|||
end
|
|||
2bd92292 | Marc Dequènes (Duck) | def self.item_modify_from_strings(item, str_list)
|
|
str_list = [str_list] unless str_list.is_a? Array
|
|||
modification_done = false
|
|||
str_list.each do |str|
|
|||
016427dd | Marc Dequènes (Duck) | unless str =~ /^(#{TEXTUI_KEY_PATTERN})(@)?(=|\+=|-=)(.*)$/
|
|
2bd92292 | Marc Dequènes (Duck) | raise SyntaxError, _("modification parameter '%s' is invalid") % str
|
|
end
|
|||
key = $1
|
|||
016427dd | Marc Dequènes (Duck) | op_modifier = $2
|
|
op = $3
|
|||
val = $4
|
|||
val = prepare_string_part(key, op_modifier, op, val)
|
|||
2bd92292 | Marc Dequènes (Duck) | ||
mod_done = item.modify(key, op, val)
|
|||
modification_done ||= mod_done
|
|||
end
|
|||
modification_done
|
|||
end
|
|||
3f984668 | Marc Dequènes (Duck) | def self.items_find_from_strings(shadow, str_list, full_search = false)
|
|
2bd92292 | Marc Dequènes (Duck) | ldap_search_objects = "(objectClass=*)"
|
|
ldap_search_aspects = ""
|
|||
98217996 | Marc Dequènes (Duck) | ldap_search_fields = Set.new
|
|
ldap_search_where =Set.new
|
|||
ldap_search_exclude =Set.new
|
|||
3f984668 | Marc Dequènes (Duck) | ldap_search_fields_post = Set.new
|
|
2bd92292 | Marc Dequènes (Duck) | str_list.each do |str|
|
|
016427dd | Marc Dequènes (Duck) | unless str =~ /^(#{TEXTUI_KEY_PATTERN})(@)?(=|~=)(.*)$/
|
|
2bd92292 | Marc Dequènes (Duck) | raise SyntaxError, _("search parameter '%s' is invalid") % str
|
|
end
|
|||
key = $1
|
|||
016427dd | Marc Dequènes (Duck) | op_modifier = $2
|
|
op = $3
|
|||
val = $4
|
|||
val = prepare_string_part(key, op_modifier, op, val)
|
|||
2bd92292 | Marc Dequènes (Duck) | ||
if key.index(":")
|
|||
type, field = key.split(":")
|
|||
else
|
|||
type = nil
|
|||
field = key
|
|||
end
|
|||
case type
|
|||
when nil
|
|||
ldap_search_fields << ldap_search_string_field(field, op, val)
|
|||
3f984668 | Marc Dequènes (Duck) | when 'filter'
|
|
unless ["=", "~="].include? op
|
|||
raise SyntaxError, _("Unknown operator '%s'") % op
|
|||
end
|
|||
if op == "~="
|
|||
begin
|
|||
new_val = Regexp.new(val)
|
|||
rescue RegexpError
|
|||
raise SyntaxError, _("Broken regexp '%s'") % val
|
|||
end
|
|||
val = new_val
|
|||
end
|
|||
ldap_search_fields_post << [field, op, val]
|
|||
2bd92292 | Marc Dequènes (Duck) | when 'rel'
|
|
raise PreProcessingError, _("Searching relations is not implemented yet")
|
|||
when ''
|
|||
case field
|
|||
when 'objects'
|
|||
ldap_search_objects = ldap_search_string_objects(shadow, field, op, val)
|
|||
when 'aspects'
|
|||
ldap_search_aspects = ldap_search_string_aspects(shadow, field, op, val)
|
|||
35b342c1 | Marc Dequènes (Duck) | when 'parents'
|
|
val.split(",").each do |parent_fh|
|
|||
parent = find_item_by_full_handle(shadow, parent_fh)
|
|||
98217996 | Marc Dequènes (Duck) | ldap_search_where << [parent.dn, :one]
|
|
35b342c1 | Marc Dequènes (Duck) | end
|
|
when 'siblings'
|
|||
val.split(",").each do |sibling_fh|
|
|||
sibling = find_item_by_full_handle(shadow, sibling_fh)
|
|||
98217996 | Marc Dequènes (Duck) | ldap_search_where << [sibling.family_parent_dn, :one]
|
|
ldap_search_exclude << sibling.dn
|
|||
35b342c1 | Marc Dequènes (Duck) | end
|
|
when 'children'
|
|||
val.split(",").each do |child_fh|
|
|||
child = find_item_by_full_handle(shadow, child_fh)
|
|||
98217996 | Marc Dequènes (Duck) | ldap_search_where << [child.family_parent_dn, :base]
|
|
35b342c1 | Marc Dequènes (Duck) | end
|
|
when 'ancestors'
|
|||
val.split(",").each do |parent_fh|
|
|||
parent = find_item_by_full_handle(shadow, parent_fh)
|
|||
98217996 | Marc Dequènes (Duck) | ldap_search_where << [parent.dn, :sub]
|
|
ldap_search_exclude << parent.dn
|
|||
35b342c1 | Marc Dequènes (Duck) | end
|
|
when 'successors'
|
|||
val.split(",").each do |child_fh|
|
|||
child = find_item_by_full_handle(shadow, child_fh)
|
|||
98217996 | Marc Dequènes (Duck) | dn = child.dn.dup
|
|
while dn != ActiveLdap::Base.base
|
|||
35b342c1 | Marc Dequènes (Duck) | dn.shift
|
|
98217996 | Marc Dequènes (Duck) | ldap_search_where << [dn, :base]
|
|
35b342c1 | Marc Dequènes (Duck) | end
|
|
end
|
|||
2bd92292 | Marc Dequènes (Duck) | else
|
|
raise PreProcessingError, _("Unknown core field '%s'") % field
|
|||
end
|
|||
else
|
|||
raise PreProcessingError, _("Unknown type '%s' for field '%s'") % [type, field]
|
|||
end
|
|||
end
|
|||
98217996 | Marc Dequènes (Duck) | ldap_search_string = "(&" + ldap_search_objects + ldap_search_aspects + ldap_search_fields.to_a.join + ")"
|
|
2bd92292 | Marc Dequènes (Duck) | ||
3f984668 | Marc Dequènes (Duck) | # default search location
|
|
35b342c1 | Marc Dequènes (Duck) | if ldap_search_where.empty?
|
|
98217996 | Marc Dequènes (Duck) | ldap_search_where << [ActiveLdap::Base.base, :sub]
|
|
35b342c1 | Marc Dequènes (Duck) | end
|
|
3f984668 | Marc Dequènes (Duck) | ||
# all attributes needed for post-search (or if full search requested)
|
|||
search_attributes = (full_search or not ldap_search_fields_post.empty?) ? nil : ["objectClass"]
|
|||
# search
|
|||
98217996 | Marc Dequènes (Duck) | res = Set.new
|
|
35b342c1 | Marc Dequènes (Duck) | ldap_search_where.each do |where|
|
|
3f984668 | Marc Dequènes (Duck) | res += ActiveLdap::Base.find(:all, :base => where[0], :scope => where[1], :filter => ldap_search_string, :attributes => search_attributes)
|
|
35b342c1 | Marc Dequènes (Duck) | end
|
|
3f984668 | Marc Dequènes (Duck) | # remove excluded entries
|
|
res.select {|item| not ldap_search_exclude.include?(item.dn) }
|
|||
# process result
|
|||
res.collect! {|item| raw_item_info(shadow, item) }
|
|||
# post search filters
|
|||
ldap_filter_results(res, ldap_search_fields_post)
|
|||
2bd92292 | Marc Dequènes (Duck) | end
|
|
def self.find_raw_item_object(shadow, raw_item)
|
|||
# easy case
|
|||
base_obj_klass = LdapShadows::Elements::LdapObject
|
|||
if raw_item.class != base_obj_klass and raw_item.class.ancestors.include?(base_obj_klass)
|
|||
return raw_item.class.handle
|
|||
end
|
|||
shadow.objects.each do |obj_hdl|
|
|||
obj_klass = shadow.get_object(obj_hdl)
|
|||
f43a4cb1 | Marc Dequènes (Duck) | ldap_classes = obj_klass.required_classes.sort
|
|
item_classes = (raw_item.classes - ['top']).sort
|
|||
return obj_hdl if (item_classes & ldap_classes == ldap_classes) and (obj_klass.excluded_classes & item_classes).empty?
|
|||
2bd92292 | Marc Dequènes (Duck) | end
|
|
nil
|
|||
end
|
|||
aa4e021c | Marc Dequènes (Duck) | def self.raw_item_info(shadow, raw_item, dn = nil)
|
|
f43a4cb1 | Marc Dequènes (Duck) | is_root = false
|
|
aa4e021c | Marc Dequènes (Duck) | if raw_item
|
|
f43a4cb1 | Marc Dequènes (Duck) | is_root = true if raw_item.dn == ActiveLdap::Base.base
|
|
aa4e021c | Marc Dequènes (Duck) | obj_hdl = self.find_raw_item_object(shadow, raw_item)
|
|
if obj_hdl
|
|||
obj_klass = shadow.get_object(obj_hdl)
|
|||
0b430a15 | Marc Dequènes (Duck) | item = obj_klass.find(is_root ? :first : raw_item.dn)
|
|
f43a4cb1 | Marc Dequènes (Duck) | ||
f341a0de | Marc Dequènes (Duck) | if item and item.dn == raw_item.dn
|
|
0b430a15 | Marc Dequènes (Duck) | name = item.full_handle
|
|
name = "root|" + name if is_root
|
|||
return {:full_handle => item.full_handle, :name => name, :item => item, :object => obj_klass, :is_root => is_root}
|
|||
end
|
|||
aa4e021c | Marc Dequènes (Duck) | end
|
|
98217996 | Marc Dequènes (Duck) | item_fake_hdl = raw_item.dn.to_s
|
|
aa4e021c | Marc Dequènes (Duck) | else
|
|
8efc9071 | Marc Dequènes (Duck) | item_fake_hdl = (dn || "???").to_s
|
|
aa4e021c | Marc Dequènes (Duck) | end
|
|
f43a4cb1 | Marc Dequènes (Duck) | {:full_handle => nil, :name => "unknown/#{item_fake_hdl}", :is_root => is_root}
|
|
aa4e021c | Marc Dequènes (Duck) | end
|
|
def self.interpret_field_value(shadow, syntax, val)
|
|||
case syntax
|
|||
when "1.3.6.1.4.1.1466.115.121.1.12"
|
|||
6d235fb6 | Marc Dequènes (Duck) | raw_item = Elements::ExternalLdapObject.find(:first, :base => val, :scope => :base)
|
|
bc31521d | Marc Dequènes (Duck) | LdapShadows::Manipulation.raw_item_info(shadow, raw_item, val.to_s)[:name]
|
|
2bd92292 | Marc Dequènes (Duck) | else
|
|
aa4e021c | Marc Dequènes (Duck) | val
|
|
2bd92292 | Marc Dequènes (Duck) | end
|
|
end
|
|||
020c18fe | Marc Dequènes (Duck) | def self.looks_like_full_handle?(full_handle)
|
|
full_handle =~ LdapShadows::Elements::LdapObject::FULL_HANDLE_PATTERN
|
|||
res = $~
|
|||
res = res.to_a if res
|
|||
res
|
|||
end
|
|||
ab983e7a | Marc Dequènes (Duck) | def self.find_item_by_full_handle(shadow, full_handle, with_admin_fields = false)
|
|
020c18fe | Marc Dequènes (Duck) | if full_handle == "root"
|
|
98217996 | Marc Dequènes (Duck) | raw_item_dn = ActiveLdap::Base.base
|
|
020c18fe | Marc Dequènes (Duck) | raw_item = ActiveLdap::Base.find(:first, :base => raw_item_dn, :scope => :base)
|
|
f341a0de | Marc Dequènes (Duck) | raise PreProcessingError, _("Root item does not exist (even if the rest of the tree may)") unless raw_item
|
|
020c18fe | Marc Dequènes (Duck) | info = raw_item_info(shadow, raw_item, raw_item_dn)
|
|
f341a0de | Marc Dequènes (Duck) | if info[:object].nil?
|
|
020c18fe | Marc Dequènes (Duck) | raise PreProcessingError, _("Root item found, but no corresponding object defined")
|
|
else
|
|||
full_handle = info[:full_handle]
|
|||
end
|
|||
end
|
|||
m = looks_like_full_handle?(full_handle)
|
|||
raise SyntaxError, _("Bad handle '%s'") % full_handle unless m
|
|||
obj_hdl = m[1].downcase.singularize
|
|||
item_hdl = m[2]
|
|||
obj_klass = shadow.get_object(obj_hdl)
|
|||
raise PreProcessingError, _("No such object '%s'") % obj_hdl if obj_klass.nil?
|
|||
ab983e7a | Marc Dequènes (Duck) | attr_list = ["*"]
|
|
attr_list << "+" if with_admin_fields
|
|||
020c18fe | Marc Dequènes (Duck) | begin
|
|
ab983e7a | Marc Dequènes (Duck) | obj_klass.find(item_hdl, :attributes => attr_list)
|
|
020c18fe | Marc Dequènes (Duck) | rescue ActiveLdap::EntryNotFound
|
|
raise PreProcessingError, _("No such item '%s/%s'") % [obj_klass.handle, item_hdl]
|
|||
end
|
|||
end
|
|||
2bd92292 | Marc Dequènes (Duck) | protected
|
|
def self.ldap_search_string_field(field, op, val)
|
|||
esc_val = ActiveLdap::Base.connection.escape_filter_value(val)
|
|||
case op
|
|||
when "="
|
|||
"(#{field}=#{esc_val})"
|
|||
when "~="
|
|||
3f984668 | Marc Dequènes (Duck) | raise PreProcessingError, _("Searching with regex is only possible for post-filters")
|
|
2bd92292 | Marc Dequènes (Duck) | ||
else
|
|||
raise SyntaxError, _("Unknown operator '%s'") % op
|
|||
end
|
|||
end
|
|||
def self.ldap_search_string_objects(shadow, field, op, val_list)
|
|||
ldap_search_parts = val_list.split(",").collect do |val|
|
|||
obj_hdl = val.downcase.singularize
|
|||
obj_klass = shadow.get_object(obj_hdl)
|
|||
raise PreProcessingError, _("No such object '%s'") % val if obj_klass.nil?
|
|||
ldap_classes = obj_klass.required_classes
|
|||
case op
|
|||
when "="
|
|||
"(&" + ldap_classes.collect{|cl| "(objectClass=#{cl})" }.join + ")"
|
|||
when "~="
|
|||
raise PreProcessingError, _("Searching with regex is not possible with objects")
|
|||
else
|
|||
raise SyntaxError, _("Unknown operator '%s'") % op
|
|||
end
|
|||
end
|
|||
"(|" + ldap_search_parts.join + ")"
|
|||
end
|
|||
def self.ldap_search_string_aspects(shadow, field, op, val_list)
|
|||
ldap_search_parts = val_list.split(",").collect do |val|
|
|||
aspect = shadow.get_aspect(val)
|
|||
raise PreProcessingError, _("No such aspect '%s'") % val if aspect.nil?
|
|||
ldap_classes = aspect.parameters[:mapping][:classes]
|
|||
case op
|
|||
when "="
|
|||
"(&" + ldap_classes.collect{|cl| "(objectClass=#{cl})" }.join + ")"
|
|||
when "~="
|
|||
raise PreProcessingError, _("Searching with regex is not possible with aspects")
|
|||
else
|
|||
raise SyntaxError, _("Unknown operator '%s'") % op
|
|||
end
|
|||
end
|
|||
"(&" + ldap_search_parts.join + ")"
|
|||
end
|
|||
3f984668 | Marc Dequènes (Duck) | ||
def self.ldap_filter_results(results, ldap_search_fields_post)
|
|||
results.collect do |item_info|
|
|||
recognized_item = item_info[:item]
|
|||
if recognized_item.nil?
|
|||
nil
|
|||
else
|
|||
found = true
|
|||
ldap_search_fields_post.each do |search_data|
|
|||
field, op, val = search_data
|
|||
found_val = false
|
|||
if recognized_item.has_field?(field)
|
|||
item_values = recognized_item.send(field.to_sym, true)
|
|||
item_values.each do |item_val|
|
|||
found_val = case op
|
|||
when "="
|
|||
item_val == val
|
|||
when "~="
|
|||
item_val =~ val
|
|||
end
|
|||
break if found_val
|
|||
end
|
|||
end
|
|||
unless found_val
|
|||
found = false
|
|||
break
|
|||
end
|
|||
end
|
|||
(found) ? item_info : nil
|
|||
end
|
|||
end.compact
|
|||
end
|
|||
2bd92292 | Marc Dequènes (Duck) | end
|
|
end
|