Significant mods to LDAP#search

This commit is contained in:
blackhedd 2006-04-25 14:03:11 +00:00
parent 193f76e530
commit 7be5474f06
3 changed files with 178 additions and 17 deletions

View file

@ -23,6 +23,7 @@ require 'net/ldap/pdu'
require 'net/ldap/filter' require 'net/ldap/filter'
require 'net/ldap/dataset' require 'net/ldap/dataset'
require 'net/ldap/psw' require 'net/ldap/psw'
require 'net/ldap/entry'
module Net module Net
@ -372,17 +373,17 @@ module Net
# Note that in the standalone case, we're permitting the caller # Note that in the standalone case, we're permitting the caller
# to modify the auth parms. # to modify the auth parms.
# #
def search args def searchx args
if @open_connection if @open_connection
@result = @open_connection.search( args ) {|values| @result = @open_connection.searchx( args ) {|values|
block_given? and yield( values ) yield( values ) if block_given?
} }
else else
@result = 0 @result = 0
conn = Connection.new( :host => @host, :port => @port ) conn = Connection.new( :host => @host, :port => @port )
if (@result = conn.bind( args[:auth] || @auth )) == 0 if (@result = conn.bind( args[:auth] || @auth )) == 0
@result = conn.search( args ) {|values| @result = conn.searchx( args ) {|values|
block_given? and yield( values ) yield( values ) if block_given?
} }
end end
conn.close conn.close
@ -391,6 +392,49 @@ module Net
@result == 0 @result == 0
end end
#--
# This is a re-implementation of search that replaces the
# original one (now renamed searchx and possibly destined to go away).
# The difference is that we return a dataset (or nil) from the
# call, and pass _each entry_ as it is received from the server
# to the caler-supplied block. This will probably make things
# far faster as we can do useful work during the network latency
# of the search. The downside is that we have no access to the
# whole set while processing the blocks, so we can't do stuff
# like sort the DNs until after the call completes.
# It's also possible that this interacts badly with server timeouts.
# We'll have to ensure that something reasonable happens if
# the caller has processed half a result set when we throw a timeout
# error.
# Another important difference is that we return a result set from
# this method rather than a T/F indication.
# Since this can be very heavy-weight, we define an argument flag
# that the caller can set to suppress the return of a result set,
# if he's planning to process every entry as it comes from the server.
#
def search args
result_set = (args and args[:return_result] == false) ? nil : {}
if @open_connection
@result = @open_connection.search( args ) {|entry|
result_set[entry.dn] = entry if result_set
yield( entry ) if block_given?
}
else
@result = 0
conn = Connection.new( :host => @host, :port => @port )
if (@result = conn.bind( args[:auth] || @auth )) == 0
@result = conn.search( args ) {|entry|
result_set[entry.dn] = entry if result_set
yield( entry ) if block_given?
}
end
conn.close
end
@result == 0 and result_set
end
# #
# bind # bind
# Bind and unbind. # Bind and unbind.
@ -505,7 +549,7 @@ module Net
raise LdapError.new( "no connection to server" ) raise LdapError.new( "no connection to server" )
end end
block_given? and yield self yield self if block_given?
end end
@ -552,15 +596,66 @@ module Net
# #
# search # search
# Alternate implementation, this yields each search entry to the caller
# as it are received.
# TODO, certain search parameters are hardcoded. # TODO, certain search parameters are hardcoded.
# TODO, if we mis-parse the server results or the results are wrong, we can block # TODO, if we mis-parse the server results or the results are wrong, we can block
# forever. That's because we keep reading results until we get a type-5 packet, # forever. That's because we keep reading results until we get a type-5 packet,
# which might never come. We need to support the time-limit in the protocol. # which might never come. We need to support the time-limit in the protocol.
#--
# WARNING: this code substantially recapitulates the searchx method.
# #
def search args def search args
search_filter = (args && args[:filter]) || Filter.eq( "objectclass", "*" ) search_filter = (args && args[:filter]) || Filter.eq( "objectclass", "*" )
search_base = (args && args[:base]) || "dc=example,dc=com" search_base = (args && args[:base]) || "dc=example,dc=com"
search_attributes = ((args && args[:attributes]) || []).map {|attr| attr.to_s.to_ber} search_attributes = ((args && args[:attributes]) || []).map {|attr| attr.to_s.to_ber}
request = [
search_base.to_ber,
2.to_ber_enumerated,
0.to_ber_enumerated,
0.to_ber,
0.to_ber,
false.to_ber,
search_filter.to_ber,
search_attributes.to_ber_sequence
].to_ber_appsequence(3)
pkt = [next_msgid.to_ber, request].to_ber_sequence
@conn.write pkt
result_code = 0
while (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be ))
case pdu.app_tag
when 4 # search-data
yield( pdu.search_entry ) if block_given?
when 5 # search-result
result_code = pdu.result_code
break
else
raise LdapError.new( "invalid response-type in search: #{pdu.app_tag}" )
end
end
result_code
end
#
# searchx
# Original implementation, this doesn't return until all data have been
# received from the server.
# TODO, certain search parameters are hardcoded.
# TODO, if we mis-parse the server results or the results are wrong, we can block
# forever. That's because we keep reading results until we get a type-5 packet,
# which might never come. We need to support the time-limit in the protocol.
#--
# WARNING: this code substantially recapitulates the search method.
#
def searchx args
search_filter = (args && args[:filter]) || Filter.eq( "objectclass", "*" )
search_base = (args && args[:base]) || "dc=example,dc=com"
search_attributes = ((args && args[:attributes]) || []).map {|attr| attr.to_s.to_ber}
request = [ request = [
search_base.to_ber, search_base.to_ber,
2.to_ber_enumerated, 2.to_ber_enumerated,
@ -665,13 +760,3 @@ module Net
end # module Net end # module Net
#------------------------------------------------------
if __FILE__ == $0
puts "No default action"
end

71
lib/net/ldap/entry.rb Normal file
View file

@ -0,0 +1,71 @@
# $Id$
#
# LDAP Entry (search-result) support classes
#
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
#
# Gmail: garbagecat10
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#---------------------------------------------------------------------------
#
module Net
class LDAP
class Entry
def initialize dn = nil
@myhash = Hash.new {|k,v| k[v] = [] }
self[:dn] = [dn]
end
def []= name, value
sym = name.to_s.downcase.intern
@myhash[sym] = value
end
def [] name
unless name.is_a?(Symbol)
name = name.to_s.downcase.intern
end
@myhash[name]
end
def dn
self[:dn].shift
end
def attribute_names
@myhash.keys
end
end # class Entry
end # class LDAP
end # module Net

View file

@ -44,7 +44,7 @@ class LdapPdu
ModifyRDNResponse = 13 ModifyRDNResponse = 13
attr_reader :msg_id, :app_tag attr_reader :msg_id, :app_tag
attr_reader :search_dn, :search_attributes attr_reader :search_dn, :search_attributes, :search_entry
# #
# initialize # initialize
@ -129,12 +129,17 @@ class LdapPdu
# NOW OBSERVE CAREFULLY: WE ARE DOWNCASING THE RETURNED ATTRIBUTE NAMES. # NOW OBSERVE CAREFULLY: WE ARE DOWNCASING THE RETURNED ATTRIBUTE NAMES.
# This is to make them more predictable for user programs, but it # This is to make them more predictable for user programs, but it
# may not be a good idea. Maybe this should be configurable. # may not be a good idea. Maybe this should be configurable.
# ALTERNATE IMPLEMENTATION: In addition to @search_dn and @search_attributes,
# we also return @search_entry, which is an LDAP::Entry object.
# If that works out well, then we'll remove the first two.
# #
def parse_search_return sequence def parse_search_return sequence
sequence.length >= 2 or raise LdapPduError sequence.length >= 2 or raise LdapPduError
@search_entry = LDAP::Entry.new( sequence[0] )
@search_dn = sequence[0] @search_dn = sequence[0]
@search_attributes = {} @search_attributes = {}
sequence[1].each {|seq| sequence[1].each {|seq|
@search_entry[seq[0]] = seq[1]
@search_attributes[seq[0].downcase.intern] = seq[1] @search_attributes[seq[0].downcase.intern] = seq[1]
} }
end end