From 463ac436a8ad0463c394feb48e40acea75d6a036 Mon Sep 17 00:00:00 2001 From: Chris Dwan Date: Wed, 16 Nov 2011 16:36:39 -0800 Subject: [PATCH 1/9] added the ability to do a delete_tree --- Gemfile | 2 ++ Gemfile.lock | 38 ++++++++++++++++++++++++++++ lib/net/ber/core_ext/array.rb | 12 +++++++++ lib/net/ldap.rb | 17 +++++++++++-- spec/unit/ber/core_ext/array_spec.rb | 14 ++++++++++ 5 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 spec/unit/ber/core_ext/array_spec.rb diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..e45e65f --- /dev/null +++ b/Gemfile @@ -0,0 +1,2 @@ +source :rubygems +gemspec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..f2c7ae4 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,38 @@ +PATH + remote: . + specs: + net-ldap (0.2.20110317223538) + +GEM + remote: http://rubygems.org/ + specs: + diff-lcs (1.1.3) + flexmock (0.9.0) + hoe (2.12.3) + rake (~> 0.8) + hoe-gemspec (1.0.0) + hoe (>= 2.2.0) + hoe-git (1.4.1) + hoe (>= 2.2.0) + metaid (1.0) + rake (0.9.2.2) + rspec (2.7.0) + rspec-core (~> 2.7.0) + rspec-expectations (~> 2.7.0) + rspec-mocks (~> 2.7.0) + rspec-core (2.7.1) + rspec-expectations (2.7.0) + diff-lcs (~> 1.1.2) + rspec-mocks (2.7.0) + +PLATFORMS + ruby + +DEPENDENCIES + flexmock (~> 0.9.0) + hoe (>= 2.9.1) + hoe-gemspec (~> 1) + hoe-git (~> 1) + metaid (~> 1) + net-ldap! + rspec (~> 2.0) diff --git a/lib/net/ber/core_ext/array.rb b/lib/net/ber/core_ext/array.rb index 8fa12c1..71b1a5e 100644 --- a/lib/net/ber/core_ext/array.rb +++ b/lib/net/ber/core_ext/array.rb @@ -79,4 +79,16 @@ module Net::BER::Extensions::Array 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 + ary = self.collect do |control_sequence| + control_sequence.collect{|element| element.to_ber}.to_ber_sequence + end + ary.to_ber_sequence #putting this on a new line to make it more readable. + end end diff --git a/lib/net/ldap.rb b/lib/net/ldap.rb index b92a13f..e687da2 100644 --- a/lib/net/ldap.rb +++ b/lib/net/ldap.rb @@ -1022,6 +1022,19 @@ class Net::LDAP @result == 0 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 => [['1.2.840.113556.1.4.805',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. @@ -1545,9 +1558,9 @@ class Net::LDAP::Connection #:nodoc: #++ 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" diff --git a/spec/unit/ber/core_ext/array_spec.rb b/spec/unit/ber/core_ext/array_spec.rb new file mode 100644 index 0000000..44e2bd9 --- /dev/null +++ b/spec/unit/ber/core_ext/array_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' +require 'metaid' + +describe Array, "when extended with BER core extensions" do + + it "should correctly convert a control code array" do + control_codes = [] + control_codes << ['1.2.3'.to_ber, true.to_ber].to_ber_sequence + control_codes << ['1.7.9'.to_ber, false.to_ber].to_ber_sequence + control_codes = control_codes.to_ber_sequence + res = [['1.2.3', true],['1.7.9',false]].to_ber_control + res.should eq(control_codes) + end +end From b6b7985d6e42744367aafc8bb29f392ee4edf220 Mon Sep 17 00:00:00 2001 From: Chris Dwan Date: Thu, 17 Nov 2011 14:23:41 -0800 Subject: [PATCH 2/9] fixes based on comments for pull request --- lib/net/ber/core_ext/array.rb | 8 +++++--- lib/net/ber/core_ext/string.rb | 10 +++++++--- lib/net/ldap.rb | 15 ++++++++------- spec/unit/ber/core_ext/array_spec.rb | 10 ++++++++++ 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/lib/net/ber/core_ext/array.rb b/lib/net/ber/core_ext/array.rb index 71b1a5e..250fa24 100644 --- a/lib/net/ber/core_ext/array.rb +++ b/lib/net/ber/core_ext/array.rb @@ -86,9 +86,11 @@ module Net::BER::Extensions::Array # [['1.2.840.113556.1.4.805',true]] # def to_ber_control - ary = self.collect do |control_sequence| - control_sequence.collect{|element| element.to_ber}.to_ber_sequence + #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 #putting this on a new line to make it more readable. + ary.to_ber_sequence.reject_empty_ber_arrays end end diff --git a/lib/net/ber/core_ext/string.rb b/lib/net/ber/core_ext/string.rb index 28aeedd..d52d787 100644 --- a/lib/net/ber/core_ext/string.rb +++ b/lib/net/ber/core_ext/string.rb @@ -46,15 +46,19 @@ module Net::BER::Extensions::String 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 diff --git a/lib/net/ldap.rb b/lib/net/ldap.rb index e687da2..b81a9d8 100644 --- a/lib/net/ldap.rb +++ b/lib/net/ldap.rb @@ -334,8 +334,9 @@ class Net::LDAP 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 + DELETE_TREE = "1.2.840.113556.1.4.805" end def self.result2string(code) #:nodoc: @@ -552,7 +553,7 @@ class Net::LDAP # anything with the bind results. We then pass self to the caller's # block, where he will execute his LDAP operations. Of course they will # all generate auth failures if the bind was unsuccessful. - raise Net::LDAP::LdapError, "Open already in progress" if @open_connection + raise LdapError, "Open already in progress" if @open_connection begin @open_connection = Net::LDAP::Connection.new(:host => @host, @@ -1033,7 +1034,7 @@ class Net::LDAP # 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 => [['1.2.840.113556.1.4.805',true]])) + delete(args.merge(:control_codes => [[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 @@ -1105,7 +1106,7 @@ class Net::LDAP #++ def paged_searches_supported? @server_caps ||= search_root_dse - @server_caps[:supportedcontrol].include?(Net::LDAP::LdapControls::PagedResults) + @server_caps[:supportedcontrol].include?(LDAPControls::PAGED_RESULTS) end end # class LDAP @@ -1402,7 +1403,7 @@ class Net::LDAP::Connection #:nodoc: controls = [] controls << [ - Net::LDAP::LdapControls::PagedResults.to_ber, + 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 @@ -1450,7 +1451,7 @@ class Net::LDAP::Connection #:nodoc: more_pages = false if result_code == 0 and controls controls.each do |c| - if c.oid == Net::LDAP::LdapControls::PagedResults + if c.oid == 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 diff --git a/spec/unit/ber/core_ext/array_spec.rb b/spec/unit/ber/core_ext/array_spec.rb index 44e2bd9..c8a6b4e 100644 --- a/spec/unit/ber/core_ext/array_spec.rb +++ b/spec/unit/ber/core_ext/array_spec.rb @@ -11,4 +11,14 @@ describe Array, "when extended with BER core extensions" do res = [['1.2.3', true],['1.7.9',false]].to_ber_control res.should eq(control_codes) end + + it "should wrap the array in another array if a nested array is not passed" do + result1 = ['1.2.3', true].to_ber_control + result2 = [['1.2.3', true]].to_ber_control + result1.should eq(result2) + end + + it "should return an empty string if an empty array is passed" do + [].to_ber_control.should be_empty + end end From 58bd212918dce30030fe68897a7774e8f92aa57e Mon Sep 17 00:00:00 2001 From: Chris Dwan Date: Thu, 17 Nov 2011 14:25:00 -0800 Subject: [PATCH 3/9] remove Gemfile.lock --- .gitignore | 1 + Gemfile.lock | 38 -------------------------------------- 2 files changed, 1 insertion(+), 38 deletions(-) delete mode 100644 Gemfile.lock diff --git a/.gitignore b/.gitignore index a323762..1959fc0 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ publish/ coverage/ coverage.info .rake_tasks~ +Gemfile.lock diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index f2c7ae4..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,38 +0,0 @@ -PATH - remote: . - specs: - net-ldap (0.2.20110317223538) - -GEM - remote: http://rubygems.org/ - specs: - diff-lcs (1.1.3) - flexmock (0.9.0) - hoe (2.12.3) - rake (~> 0.8) - hoe-gemspec (1.0.0) - hoe (>= 2.2.0) - hoe-git (1.4.1) - hoe (>= 2.2.0) - metaid (1.0) - rake (0.9.2.2) - rspec (2.7.0) - rspec-core (~> 2.7.0) - rspec-expectations (~> 2.7.0) - rspec-mocks (~> 2.7.0) - rspec-core (2.7.1) - rspec-expectations (2.7.0) - diff-lcs (~> 1.1.2) - rspec-mocks (2.7.0) - -PLATFORMS - ruby - -DEPENDENCIES - flexmock (~> 0.9.0) - hoe (>= 2.9.1) - hoe-gemspec (~> 1) - hoe-git (~> 1) - metaid (~> 1) - net-ldap! - rspec (~> 2.0) From 63db8c836a78c931c7922b6ee841dd67b24c1d5b Mon Sep 17 00:00:00 2001 From: Michael Baker Date: Thu, 17 Nov 2011 23:32:03 +0000 Subject: [PATCH 4/9] Bring back the Net::LDAP namespace --- lib/net/ldap.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/net/ldap.rb b/lib/net/ldap.rb index b81a9d8..35b3ddf 100644 --- a/lib/net/ldap.rb +++ b/lib/net/ldap.rb @@ -553,7 +553,7 @@ class Net::LDAP # anything with the bind results. We then pass self to the caller's # block, where he will execute his LDAP operations. Of course they will # all generate auth failures if the bind was unsuccessful. - raise LdapError, "Open already in progress" if @open_connection + raise Net::LDAP::LdapError, "Open already in progress" if @open_connection begin @open_connection = Net::LDAP::Connection.new(:host => @host, @@ -1034,7 +1034,7 @@ class Net::LDAP # 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 => [[LDAPControls::DELETE_TREE, true]])) + 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 @@ -1106,7 +1106,7 @@ class Net::LDAP #++ def paged_searches_supported? @server_caps ||= search_root_dse - @server_caps[:supportedcontrol].include?(LDAPControls::PAGED_RESULTS) + @server_caps[:supportedcontrol].include?(Net::LDAP::LDAPControls::PAGED_RESULTS) end end # class LDAP @@ -1403,7 +1403,7 @@ class Net::LDAP::Connection #:nodoc: controls = [] controls << [ - LDAPControls::PAGED_RESULTS.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 @@ -1451,7 +1451,7 @@ class Net::LDAP::Connection #:nodoc: more_pages = false if result_code == 0 and controls controls.each do |c| - if c.oid == LDAPControls::PAGED_RESULTS + 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 From 2763040162d6ec432234c21d4c6a837c559c3ac2 Mon Sep 17 00:00:00 2001 From: Michael Baker Date: Tue, 29 Nov 2011 22:01:34 +0000 Subject: [PATCH 5/9] Return PDU instead of result code --- lib/net/ldap.rb | 57 ++++++++++++++++++++--------------- lib/net/ldap/pdu.rb | 8 +++++ spec/unit/ldap/search_spec.rb | 7 ++--- spec/unit/ldap_spec.rb | 32 +++++++++++++++++++- 4 files changed, 74 insertions(+), 30 deletions(-) diff --git a/lib/net/ldap.rb b/lib/net/ldap.rb index 35b3ddf..fe31ce8 100644 --- a/lib/net/ldap.rb +++ b/lib/net/ldap.rb @@ -629,11 +629,10 @@ class Net::LDAP 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 +644,9 @@ class Net::LDAP 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 +720,7 @@ class Net::LDAP end end - @result == 0 + @result end # #bind_as is for testing authentication credentials. @@ -816,14 +815,14 @@ class Net::LDAP 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 +913,15 @@ class Net::LDAP 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 +985,14 @@ class Net::LDAP 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,14 +1013,14 @@ class Net::LDAP 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. @@ -1250,7 +1250,7 @@ class Net::LDAP::Connection #:nodoc: (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 #-- @@ -1288,7 +1288,7 @@ class Net::LDAP::Connection #:nodoc: @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) @@ -1374,7 +1374,7 @@ class Net::LDAP::Connection #:nodoc: # 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 { @@ -1413,7 +1413,7 @@ class Net::LDAP::Connection #:nodoc: pkt = [next_msgid.to_ber, request, controls].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)) @@ -1430,7 +1430,7 @@ class Net::LDAP::Connection #:nodoc: end end when 5 # search-result - result_code = pdu.result_code + result_pdu = pdu controls = pdu.result_controls break else @@ -1449,7 +1449,7 @@ class Net::LDAP::Connection #:nodoc: # 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::PAGED_RESULTS # just in case some bogus server sends us more than 1 of these. @@ -1468,7 +1468,7 @@ class Net::LDAP::Connection #:nodoc: break unless more_pages } # loop - result_code + result_pdu || OpenStruct.new(:status => :failure, :result_code => 1, :message => "Invalid search") end MODIFY_OPERATIONS = { #:nodoc: @@ -1508,7 +1508,8 @@ class Net::LDAP::Connection #:nodoc: @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 #-- @@ -1529,8 +1530,12 @@ class Net::LDAP::Connection #:nodoc: 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 #-- @@ -1551,7 +1556,8 @@ class Net::LDAP::Connection #:nodoc: (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 #-- @@ -1565,6 +1571,7 @@ class Net::LDAP::Connection #:nodoc: @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 diff --git a/lib/net/ldap/pdu.rb b/lib/net/ldap/pdu.rb index bdde92c..37c3c08 100644 --- a/lib/net/ldap/pdu.rb +++ b/lib/net/ldap/pdu.rb @@ -112,6 +112,10 @@ class Net::LDAP::PDU @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,10 @@ class Net::LDAP::PDU @ldap_result and @ldap_result[code] end + def status + result_code == 0 ? :success : :failure + end + ## # Return serverSaslCreds, which are only present in BindResponse packets. #-- diff --git a/spec/unit/ldap/search_spec.rb b/spec/unit/ldap/search_spec.rb index 61cb4fd..8f9446c 100644 --- a/spec/unit/ldap/search_spec.rb +++ b/spec/unit/ldap/search_spec.rb @@ -3,8 +3,7 @@ describe Net::LDAP, "search method" do class FakeConnection def search(args) - error_code = 1 - return error_code + OpenStruct.new(:result_code => 1, :message => "error") end end @@ -22,8 +21,8 @@ describe Net::LDAP, "search method" do context "when :return_result => false" do it "should return false upon error" do - success = @connection.search(:return_result => false) - success.should == false + result = @connection.search(:return_result => false) + result.result_code.should == 1 end end diff --git a/spec/unit/ldap_spec.rb b/spec/unit/ldap_spec.rb index 1edb5c9..0cf91ae 100644 --- a/spec/unit/ldap_spec.rb +++ b/spec/unit/ldap_spec.rb @@ -45,4 +45,34 @@ describe Net::LDAP::Connection do end end end -end \ No newline at end of file + + context "populate error messages" do + before do + @tcp_socket = flexmock(:connection) + @tcp_socket.should_receive(:write) + flexmock(TCPSocket).should_receive(:new).and_return(@tcp_socket) + end + + subject { Net::LDAP::Connection.new(:server => 'test.mocked.com', :port => 636) } + + it "should get back error messages if operation fails" do + ber = Net::BER::BerIdentifiedArray.new([53, "", "The provided password value was rejected by a password validator: The provided password did not contain enough characters from the character set 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. The minimum number of characters from that set that must be present in user passwords is 1"]) + ber.ber_identifier = 7 + @tcp_socket.should_receive(:read_ber).and_return([2, ber]) + + result = subject.modify(:dn => "1", :operations => [[:replace, "mail", "something@sothsdkf.com"]]) + result.status.should == :failure + result.error_message.should == "The provided password value was rejected by a password validator: The provided password did not contain enough characters from the character set 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. The minimum number of characters from that set that must be present in user passwords is 1" + end + + it "shouldn't get back error messages if operation succeeds" do + ber = Net::BER::BerIdentifiedArray.new([0, "", ""]) + ber.ber_identifier = 7 + @tcp_socket.should_receive(:read_ber).and_return([2, ber]) + + result = subject.modify(:dn => "1", :operations => [[:replace, "mail", "something@sothsdkf.com"]]) + result.status.should == :success + result.error_message.should == "" + end + end +end From 40f0e1857ee619250051d7d37f48a5c03424a29f Mon Sep 17 00:00:00 2001 From: Michael Baker Date: Wed, 30 Nov 2011 20:03:02 +0000 Subject: [PATCH 6/9] Add success and failure methods. --- lib/net/ldap/pdu.rb | 8 ++++++++ spec/unit/ldap_spec.rb | 16 ++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/net/ldap/pdu.rb b/lib/net/ldap/pdu.rb index 37c3c08..b771fab 100644 --- a/lib/net/ldap/pdu.rb +++ b/lib/net/ldap/pdu.rb @@ -128,6 +128,14 @@ class Net::LDAP::PDU result_code == 0 ? :success : :failure end + def success? + status == :success + end + + def failure? + !success? + end + ## # Return serverSaslCreds, which are only present in BindResponse packets. #-- diff --git a/spec/unit/ldap_spec.rb b/spec/unit/ldap_spec.rb index 0cf91ae..272d4ee 100644 --- a/spec/unit/ldap_spec.rb +++ b/spec/unit/ldap_spec.rb @@ -7,11 +7,11 @@ describe Net::LDAP::Connection do flexmock(TCPSocket). should_receive(:new).and_raise(Errno::ECONNREFUSED) end - + it "should raise LdapError" do lambda { Net::LDAP::Connection.new( - :server => 'test.mocked.com', + :server => 'test.mocked.com', :port => 636) }.should raise_error(Net::LDAP::LdapError) end @@ -21,11 +21,11 @@ describe Net::LDAP::Connection do flexmock(TCPSocket). should_receive(:new).and_raise(SocketError) end - + it "should raise LdapError" do lambda { Net::LDAP::Connection.new( - :server => 'test.mocked.com', + :server => 'test.mocked.com', :port => 636) }.should raise_error(Net::LDAP::LdapError) end @@ -35,11 +35,11 @@ describe Net::LDAP::Connection do flexmock(TCPSocket). should_receive(:new).and_raise(NameError) end - + it "should rethrow the exception" do lambda { Net::LDAP::Connection.new( - :server => 'test.mocked.com', + :server => 'test.mocked.com', :port => 636) }.should raise_error(NameError) end @@ -61,7 +61,7 @@ describe Net::LDAP::Connection do @tcp_socket.should_receive(:read_ber).and_return([2, ber]) result = subject.modify(:dn => "1", :operations => [[:replace, "mail", "something@sothsdkf.com"]]) - result.status.should == :failure + result.should be_failure result.error_message.should == "The provided password value was rejected by a password validator: The provided password did not contain enough characters from the character set 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. The minimum number of characters from that set that must be present in user passwords is 1" end @@ -71,7 +71,7 @@ describe Net::LDAP::Connection do @tcp_socket.should_receive(:read_ber).and_return([2, ber]) result = subject.modify(:dn => "1", :operations => [[:replace, "mail", "something@sothsdkf.com"]]) - result.status.should == :success + result.should be_success result.error_message.should == "" end end From 4c24b4ea3696db2a31eab287951676af3f351be5 Mon Sep 17 00:00:00 2001 From: Chris Dwan Date: Thu, 12 Jan 2012 14:50:12 -0800 Subject: [PATCH 7/9] Added encoding of sort controls if passed in as an option to the search --- lib/net/ldap.rb | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/lib/net/ldap.rb b/lib/net/ldap.rb index fe31ce8..85501cb 100644 --- a/lib/net/ldap.rb +++ b/lib/net/ldap.rb @@ -336,6 +336,8 @@ class Net::LDAP 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 @@ -1328,6 +1330,35 @@ class Net::LDAP::Connection #:nodoc: 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. @@ -1353,6 +1384,7 @@ class Net::LDAP::Connection #:nodoc: 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 @@ -1408,6 +1440,8 @@ class Net::LDAP::Connection #:nodoc: 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.to_ber_contextspecific(0) pkt = [next_msgid.to_ber, request, controls].to_ber_sequence From 09e372ee6352d2e0289dceb7ef753ab0ee393358 Mon Sep 17 00:00:00 2001 From: MichaelBaker Date: Mon, 30 Jan 2012 20:07:45 -0800 Subject: [PATCH 8/9] Update net-ldap.gemspec --- net-ldap.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net-ldap.gemspec b/net-ldap.gemspec index 00a99e4..5cfe450 100644 --- a/net-ldap.gemspec +++ b/net-ldap.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |s| s.name = %q{net-ldap} - s.version = "0.2.20110317223538" + s.version = "0.2.20110317223539" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Francis Cianfrocca", "Emiel van de Laar", "Rory O'Connell", "Kaspar Schiess", "Austin Ziegler"] From ad4493b1040e58f73c26b260335cfd11e68a36dc Mon Sep 17 00:00:00 2001 From: MichaelBaker Date: Mon, 30 Jan 2012 20:10:29 -0800 Subject: [PATCH 9/9] Update net-ldap.gemspec --- net-ldap.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net-ldap.gemspec b/net-ldap.gemspec index 5cfe450..4ac94f9 100644 --- a/net-ldap.gemspec +++ b/net-ldap.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |s| s.name = %q{net-ldap} - s.version = "0.2.20110317223539" + s.version = "0.3.20110317223539" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Francis Cianfrocca", "Emiel van de Laar", "Rory O'Connell", "Kaspar Schiess", "Austin Ziegler"]