Permalink
Browse files

Merge

  • Loading branch information...
Rory OConnell
Rory OConnell committed Feb 29, 2012
2 parents 3345c58 + ad4493b commit 4ab764558f5e682f28a118c275ac1d7a7ee837ba
View
@@ -9,3 +9,4 @@ publish/
coverage/
coverage.info
.rake_tasks~
+Gemfile.lock
View
@@ -0,0 +1,2 @@
+source :rubygems
+gemspec
@@ -79,4 +79,18 @@ def to_ber_oid
oid = ary.pack("w*")
[6, oid.length].pack("CC") + oid
end
+
+ ##
+ # Converts an array into a set of ber control codes
+ # The expected format is [[control_oid, criticality, control_value(optional)]]
+ # [['1.2.840.113556.1.4.805',true]]
+ #
+ def to_ber_control
+ #if our array does not contain at least one array then wrap it in an array before going forward
+ ary = self[0].kind_of?(Array) ? self : [self]
+ ary = ary.collect do |control_sequence|
+ control_sequence.collect{|element| element.to_ber}.to_ber_sequence.reject_empty_ber_arrays
+ end
+ ary.to_ber_sequence.reject_empty_ber_arrays
+ end
end
@@ -46,15 +46,19 @@ def to_ber_contextspecific(code)
def read_ber(syntax = nil)
StringIO.new(self).read_ber(syntax)
end
-
+
##
- # Destructively reads a BER object from the string.
+ # Destructively reads a BER object from the string.
def read_ber!(syntax = nil)
io = StringIO.new(self)
result = io.read_ber(syntax)
self.slice!(0...io.pos)
-
+
return result
end
+
+ def reject_empty_ber_arrays
+ self.gsub(/0\000/n,'')
+ end
end
View
@@ -335,8 +335,11 @@ class LdapError < StandardError; end
68 => "Entry Already Exists"
}
- module LdapControls
- PagedResults = "1.2.840.113556.1.4.319" # Microsoft evil from RFC 2696
+ module LDAPControls
+ PAGED_RESULTS = "1.2.840.113556.1.4.319" # Microsoft evil from RFC 2696
+ SORT_REQUEST = "1.2.840.113556.1.4.473"
+ SORT_RESPONSE = "1.2.840.113556.1.4.474"
+ DELETE_TREE = "1.2.840.113556.1.4.805"
end
def self.result2string(code) #:nodoc:
@@ -629,11 +632,10 @@ def search(args = {})
yield entry if block_given?
}
else
- @result = 0
begin
conn = Net::LDAP::Connection.new(:host => @host, :port => @port,
:encryption => @encryption)
- if (@result = conn.bind(args[:auth] || @auth)) == 0
+ if (@result = conn.bind(args[:auth] || @auth)).result_code == 0
@result = conn.search(args) { |entry|
result_set << entry if result_set
yield entry if block_given?
@@ -645,9 +647,9 @@ def search(args = {})
end
if return_result_set
- @result == 0 ? result_set : nil
+ (!@result.nil? && @result.result_code == 0) ? result_set : nil
else
- @result == 0
+ @result
end
end
@@ -721,7 +723,7 @@ def bind(auth = @auth)
end
end
- @result == 0
+ @result
end
# #bind_as is for testing authentication credentials.
@@ -816,14 +818,14 @@ def add(args)
begin
conn = Connection.new(:host => @host, :port => @port,
:encryption => @encryption)
- if (@result = conn.bind(args[:auth] || @auth)) == 0
+ if (@result = conn.bind(args[:auth] || @auth)).result_code == 0
@result = conn.add(args)
end
ensure
conn.close if conn
end
end
- @result == 0
+ @result
end
# Modifies the attribute values of a particular entry on the LDAP
@@ -914,14 +916,15 @@ def modify(args)
begin
conn = Connection.new(:host => @host, :port => @port,
:encryption => @encryption)
- if (@result = conn.bind(args[:auth] || @auth)) == 0
+ if (@result = conn.bind(args[:auth] || @auth)).result_code == 0
@result = conn.modify(args)
end
ensure
conn.close if conn
end
end
- @result == 0
+
+ @result
end
# Add a value to an attribute. Takes the full DN of the entry to modify,
@@ -985,14 +988,14 @@ def rename(args)
begin
conn = Connection.new(:host => @host, :port => @port,
:encryption => @encryption)
- if (@result = conn.bind(args[:auth] || @auth)) == 0
+ if (@result = conn.bind(args[:auth] || @auth)).result_code == 0
@result = conn.rename(args)
end
ensure
conn.close if conn
end
end
- @result == 0
+ @result
end
alias_method :modify_rdn, :rename
@@ -1013,16 +1016,29 @@ def delete(args)
begin
conn = Connection.new(:host => @host, :port => @port,
:encryption => @encryption)
- if (@result = conn.bind(args[:auth] || @auth)) == 0
+ if (@result = conn.bind(args[:auth] || @auth)).result_code == 0
@result = conn.delete(args)
end
ensure
conn.close
end
end
- @result == 0
+ @result
end
+ # Delete an entry from the LDAP directory along with all subordinate entries.
+ # the regular delete method will fail to delete an entry if it has subordinate
+ # entries. This method sends an extra control code to tell the LDAP server
+ # to do a tree delete. ('1.2.840.113556.1.4.805')
+ #
+ # Returns True or False to indicate whether the delete succeeded. Extended
+ # status information is available by calling #get_operation_result.
+ #
+ # dn = "mail=deleteme@example.com, ou=people, dc=example, dc=com"
+ # ldap.delete_tree :dn => dn
+ def delete_tree(args)
+ delete(args.merge(:control_codes => [[Net::LDAP::LDAPControls::DELETE_TREE, true]]))
+ end
# This method is experimental and subject to change. Return the rootDSE
# record from the LDAP server as a Net::LDAP::Entry, or an empty Entry if
# the server doesn't return the record.
@@ -1093,7 +1109,7 @@ def search_subschema_entry
#++
def paged_searches_supported?
@server_caps ||= search_root_dse
- @server_caps[:supportedcontrol].include?(Net::LDAP::LdapControls::PagedResults)
+ @server_caps[:supportedcontrol].include?(Net::LDAP::LDAPControls::PAGED_RESULTS)
end
end # class LDAP
@@ -1237,7 +1253,7 @@ def bind_simple(auth)
(be = @conn.read_ber(Net::LDAP::AsnSyntax) and pdu = Net::LDAP::PDU.new(be)) or raise Net::LDAP::LdapError, "no bind result"
- pdu.result_code
+ pdu
end
#--
@@ -1275,7 +1291,7 @@ def bind_sasl(auth)
@conn.write request_pkt
(be = @conn.read_ber(Net::LDAP::AsnSyntax) and pdu = Net::LDAP::PDU.new(be)) or raise Net::LDAP::LdapError, "no bind result"
- return pdu.result_code unless pdu.result_code == 14 # saslBindInProgress
+ return pdu unless pdu.result_code == 14 # saslBindInProgress
raise Net::LDAP::LdapError, "sasl-challenge overflow" if ((n += 1) > MaxSaslChallenges)
cred = chall.call(pdu.result_server_sasl_creds)
@@ -1315,6 +1331,35 @@ def bind_gss_spnego(auth)
end
private :bind_gss_spnego
+
+ #--
+ # Allow the caller to specify a sort control
+ #
+ # The format of the sort control needs to be:
+ #
+ # :sort_control => ["cn"] # just a string
+ # or
+ # :sort_control => [["cn", "matchingRule", true]] #attribute, matchingRule, direction (true / false)
+ # or
+ # :sort_control => ["givenname","sn"] #multiple strings or arrays
+ #
+ def encode_sort_controls(sort_definitions)
+ return sort_definitions unless sort_definitions
+
+ sort_control_values = sort_definitions.map do |control|
+ control = Array(control) # if there is only an attribute name as a string then infer the orderinrule and reverseorder
+ control[0] = String(control[0]).to_ber,
+ control[1] = String(control[1]).to_ber,
+ control[2] = (control[2] == true).to_ber
+ control.to_ber_sequence
+ end
+ sort_control = [
+ Net::LDAP::LDAPControls::SORT_REQUEST.to_ber,
+ false.to_ber,
+ sort_control_values.to_ber_sequence.to_s.to_ber
+ ].to_ber_sequence
+ end
+
#--
# Alternate implementation, this yields each search entry to the caller as
# it are received.
@@ -1340,6 +1385,7 @@ def search(args = {})
scope = args[:scope] || Net::LDAP::SearchScope_WholeSubtree
raise Net::LDAP::LdapError, "invalid search scope" unless Net::LDAP::SearchScopes.include?(scope)
+ sort_control = encode_sort_controls(args.fetch(:sort_controls){ false })
# An interesting value for the size limit would be close to A/D's
# built-in page limit of 1000 records, but openLDAP newer than version
# 2.2.0 chokes on anything bigger than 126. You get a silent error that
@@ -1361,7 +1407,7 @@ def search(args = {})
# to do a root-DSE record search and not do a paged search if the LDAP
# doesn't support it. Yuck.
rfc2696_cookie = [126, ""]
- result_code = 0
+ result_pdu = nil
n_results = 0
loop {
@@ -1390,17 +1436,18 @@ def search(args = {})
controls = []
controls <<
[
- Net::LDAP::LdapControls::PagedResults.to_ber,
+ Net::LDAP::LDAPControls::PAGED_RESULTS.to_ber,
# Criticality MUST be false to interoperate with normal LDAPs.
false.to_ber,
rfc2696_cookie.map{ |v| v.to_ber}.to_ber_sequence.to_s.to_ber
].to_ber_sequence if paged_searches_supported
+ controls << sort_control if sort_control
controls = controls.empty? ? nil : controls.to_ber_contextspecific(0)
pkt = [next_msgid.to_ber, request, controls].compact.to_ber_sequence
@conn.write pkt
- result_code = 0
+ result_pdu = nil
controls = []
while (be = @conn.read_ber(Net::LDAP::AsnSyntax)) && (pdu = Net::LDAP::PDU.new(be))
@@ -1417,7 +1464,7 @@ def search(args = {})
end
end
when 5 # search-result
- result_code = pdu.result_code
+ result_pdu = pdu
controls = pdu.result_controls
if return_referrals && result_code == 10
if block_given?
@@ -1443,9 +1490,9 @@ def search(args = {})
# of type OCTET STRING, covered in the default syntax supported by
# read_ber, so I guess we're ok.
more_pages = false
- if result_code == 0 and controls
+ if result_pdu.result_code == 0 and controls
controls.each do |c|
- if c.oid == Net::LDAP::LdapControls::PagedResults
+ if c.oid == Net::LDAP::LDAPControls::PAGED_RESULTS
# just in case some bogus server sends us more than 1 of these.
more_pages = false
if c.value and c.value.length > 0
@@ -1462,7 +1509,7 @@ def search(args = {})
break unless more_pages
} # loop
- result_code
+ result_pdu || OpenStruct.new(:status => :failure, :result_code => 1, :message => "Invalid search")
end
MODIFY_OPERATIONS = { #:nodoc:
@@ -1502,7 +1549,8 @@ def modify(args)
@conn.write pkt
(be = @conn.read_ber(Net::LDAP::AsnSyntax)) && (pdu = Net::LDAP::PDU.new(be)) && (pdu.app_tag == 7) or raise Net::LDAP::LdapError, "response missing or invalid"
- pdu.result_code
+
+ pdu
end
#--
@@ -1523,8 +1571,12 @@ def add(args)
pkt = [next_msgid.to_ber, request].to_ber_sequence
@conn.write pkt
- (be = @conn.read_ber(Net::LDAP::AsnSyntax)) && (pdu = Net::LDAP::PDU.new(be)) && (pdu.app_tag == 9) or raise Net::LDAP::LdapError, "response missing or invalid"
- pdu.result_code
+ (be = @conn.read_ber(Net::LDAP::AsnSyntax)) &&
+ (pdu = Net::LDAP::PDU.new(be)) &&
+ (pdu.app_tag == 9) or
+ raise Net::LDAP::LdapError, "response missing or invalid"
+
+ pdu
end
#--
@@ -1545,20 +1597,22 @@ def rename args
(be = @conn.read_ber(Net::LDAP::AsnSyntax)) &&
(pdu = Net::LDAP::PDU.new( be )) && (pdu.app_tag == 13) or
raise Net::LDAP::LdapError.new( "response missing or invalid" )
- pdu.result_code
+
+ pdu
end
#--
# TODO, need to support a time limit, in case the server fails to respond.
#++
def delete(args)
dn = args[:dn] or raise "Unable to delete empty DN"
-
+ controls = args.include?(:control_codes) ? args[:control_codes].to_ber_control : nil #use nil so we can compact later
request = dn.to_s.to_ber_application_string(10)
- pkt = [next_msgid.to_ber, request].to_ber_sequence
+ pkt = [next_msgid.to_ber, request, controls].compact.to_ber_sequence
@conn.write pkt
(be = @conn.read_ber(Net::LDAP::AsnSyntax)) && (pdu = Net::LDAP::PDU.new(be)) && (pdu.app_tag == 11) or raise Net::LDAP::LdapError, "response missing or invalid"
- pdu.result_code
+
+ pdu
end
end # class Connection
View
@@ -112,6 +112,10 @@ def result
@ldap_result || {}
end
+ def error_message
+ result[:errorMessage] || ""
+ end
+
##
# This returns an LDAP result code taken from the PDU, but it will be nil
# if there wasn't a result code. That can easily happen depending on the
@@ -120,6 +124,18 @@ def result_code(code = :resultCode)
@ldap_result and @ldap_result[code]
end
+ def status
+ result_code == 0 ? :success : :failure
+ end
+
+ def success?
+ status == :success
+ end
+
+ def failure?
+ !success?
+ end
+
##
# Return serverSaslCreds, which are only present in BindResponse packets.
#--
Oops, something went wrong.

0 comments on commit 4ab7645

Please sign in to comment.