Merge
This commit is contained in:
commit
4ab764558f
10 changed files with 191 additions and 47 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,3 +9,4 @@ publish/
|
|||
coverage/
|
||||
coverage.info
|
||||
.rake_tasks~
|
||||
Gemfile.lock
|
||||
|
|
2
Gemfile
Normal file
2
Gemfile
Normal file
|
@ -0,0 +1,2 @@
|
|||
source :rubygems
|
||||
gemspec
|
|
@ -79,4 +79,18 @@ 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
|
||||
#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 @@ 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
|
||||
|
|
118
lib/net/ldap.rb
118
lib/net/ldap.rb
|
@ -335,8 +335,11 @@ 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
|
||||
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 @@ 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 +647,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 +723,7 @@ class Net::LDAP
|
|||
end
|
||||
end
|
||||
|
||||
@result == 0
|
||||
@result
|
||||
end
|
||||
|
||||
# #bind_as is for testing authentication credentials.
|
||||
|
@ -816,14 +818,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 +916,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 +988,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,16 +1016,29 @@ 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.
|
||||
# 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 @@ class Net::LDAP
|
|||
#++
|
||||
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 @@ 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
|
||||
|
||||
#--
|
||||
|
@ -1275,7 +1291,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)
|
||||
|
@ -1315,6 +1331,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.
|
||||
|
@ -1340,6 +1385,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
|
||||
|
@ -1361,7 +1407,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 {
|
||||
|
@ -1390,17 +1436,18 @@ class Net::LDAP::Connection #:nodoc:
|
|||
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 @@ class Net::LDAP::Connection #:nodoc:
|
|||
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 @@ 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::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 @@ 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:
|
||||
|
@ -1502,7 +1549,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
|
||||
|
||||
#--
|
||||
|
@ -1523,8 +1571,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
|
||||
|
||||
#--
|
||||
|
@ -1545,7 +1597,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
|
||||
|
||||
#--
|
||||
|
@ -1553,12 +1606,13 @@ 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"
|
||||
pdu.result_code
|
||||
|
||||
pdu
|
||||
end
|
||||
end # class Connection
|
||||
|
|
|
@ -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,18 @@ class Net::LDAP::PDU
|
|||
@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.
|
||||
#--
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
Gem::Specification.new do |s|
|
||||
s.name = %q{net-ldap}
|
||||
s.version = "0.3.1"
|
||||
s.version = "0.3.2"
|
||||
|
||||
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"]
|
||||
|
|
24
spec/unit/ber/core_ext/array_spec.rb
Normal file
24
spec/unit/ber/core_ext/array_spec.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
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
|
||||
|
||||
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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,14 +35,44 @@ 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
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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.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
|
||||
|
||||
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.should be_success
|
||||
result.error_message.should == ""
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue