lib/net/ldap/connection.rb in net-ldap-0.13.0 vs lib/net/ldap/connection.rb in net-ldap-0.14.0
- old
+ new
@@ -7,23 +7,32 @@
DefaultConnectTimeout = 5
LdapVersion = 3
MaxSaslChallenges = 10
- def initialize(server)
+ # Initialize a connection to an LDAP server
+ #
+ # :server
+ # :hosts Array of tuples specifying host, port
+ # :host host
+ # :port port
+ # :socket prepared socket
+ #
+ def initialize(server = {})
+ @server = server
@instrumentation_service = server[:instrumentation_service]
- if server[:socket]
- prepare_socket(server)
- else
- server[:hosts] = [[server[:host], server[:port]]] if server[:hosts].nil?
- open_connection(server)
- end
+ # Allows tests to parameterize what socket class to use
+ @socket_class = server.fetch(:socket_class, DefaultSocket)
yield self if block_given?
end
+ def socket_class=(socket_class)
+ @socket_class = socket_class
+ end
+
def prepare_socket(server)
socket = server[:socket]
encryption = server[:encryption]
@conn = socket
@@ -33,17 +42,17 @@
def open_connection(server)
hosts = server[:hosts]
encryption = server[:encryption]
socket_opts = {
- connect_timeout: server[:connect_timeout] || DefaultConnectTimeout
+ connect_timeout: server[:connect_timeout] || DefaultConnectTimeout,
}
errors = []
hosts.each do |host, port|
begin
- prepare_socket(server.merge(socket: Socket.tcp(host, port, socket_opts)))
+ prepare_socket(server.merge(socket: @socket_class.new(host, port, socket_opts)))
return
rescue Net::LDAP::Error, SocketError, SystemCallError,
OpenSSL::SSL::SSLError => e
# Ensure the connection is closed in the event a setup failure.
close
@@ -122,11 +131,11 @@
# additional branches requiring server validation and peer certs, etc.
# go here.
when :start_tls
message_id = next_msgid
request = [
- Net::LDAP::StartTlsOid.to_ber_contextspecific(0)
+ Net::LDAP::StartTlsOid.to_ber_contextspecific(0),
].to_ber_appsequence(Net::LDAP::PDU::ExtendedRequest)
write(request, nil, message_id)
pdu = queued_read(message_id)
@@ -200,11 +209,11 @@
#
# Returns parsed Net::LDAP::PDU object.
def read(syntax = Net::LDAP::AsnSyntax)
ber_object =
instrument "read.net_ldap_connection", :syntax => syntax do |payload|
- @conn.read_ber(syntax) do |id, content_length|
+ socket.read_ber(syntax) do |id, content_length|
payload[:object_type_id] = id
payload[:content_length] = content_length
end
end
@@ -230,11 +239,11 @@
# Returns the return value from writing to the connection, which in some
# cases is the Integer number of bytes written to the socket.
def write(request, controls = nil, message_id = next_msgid)
instrument "write.net_ldap_connection" do |payload|
packet = [message_id.to_ber, request, controls].compact.to_ber_sequence
- payload[:content_length] = @conn.write(packet)
+ payload[:content_length] = socket.write(packet)
end
end
private :write
def next_msgid
@@ -272,11 +281,11 @@
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
+ 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
@@ -385,11 +394,11 @@
deref.to_ber_enumerated,
query_limit.to_ber, # size limit
time.to_ber,
attrs_only.to_ber,
filter.to_ber,
- ber_attrs.to_ber_sequence
+ ber_attrs.to_ber_sequence,
].to_ber_appsequence(Net::LDAP::PDU::SearchRequest)
# rfc2696_cookie sometimes contains binary data from Microsoft Active Directory
# this breaks when calling to_ber. (Can't force binary data to UTF-8)
# we have to disable paging (even though server supports it) to get around this...
@@ -398,11 +407,11 @@
controls <<
[
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
+ rfc2696_cookie.map(&:to_ber).to_ber_sequence.to_s.to_ber,
].to_ber_sequence if paged
controls << ber_sort if ber_sort
controls = controls.empty? ? nil : controls.to_ber_contextspecific(0)
write(request, controls, message_id)
@@ -492,24 +501,24 @@
end
MODIFY_OPERATIONS = { #:nodoc:
:add => 0,
:delete => 1,
- :replace => 2
+ :replace => 2,
}
def self.modify_ops(operations)
ops = []
if operations
- operations.each { |op, attrib, values|
+ operations.each do |op, attrib, values|
# TODO, fix the following line, which gives a bogus error if the
# opcode is invalid.
op_ber = MODIFY_OPERATIONS[op.to_sym].to_ber_enumerated
- values = [ values ].flatten.map { |v| v.to_ber if v }.to_ber_set
- values = [ attrib.to_s.to_ber, values ].to_ber_sequence
- ops << [ op_ber, values ].to_ber
- }
+ values = [values].flatten.map { |v| v.to_ber if v }.to_ber_set
+ values = [attrib.to_s.to_ber, values].to_ber_sequence
+ ops << [op_ber, values].to_ber
+ end
end
ops
end
#--
@@ -524,11 +533,11 @@
ops = self.class.modify_ops args[:operations]
message_id = next_msgid
request = [
modify_dn.to_ber,
- ops.to_ber_sequence
+ ops.to_ber_sequence,
].to_ber_appsequence(Net::LDAP::PDU::ModifyRequest)
write(request, nil, message_id)
pdu = queued_read(message_id)
@@ -537,23 +546,68 @@
end
pdu
end
+ ##
+ # Password Modify
+ #
+ # http://tools.ietf.org/html/rfc3062
+ #
+ # passwdModifyOID OBJECT IDENTIFIER ::= 1.3.6.1.4.1.4203.1.11.1
+ #
+ # PasswdModifyRequestValue ::= SEQUENCE {
+ # userIdentity [0] OCTET STRING OPTIONAL
+ # oldPasswd [1] OCTET STRING OPTIONAL
+ # newPasswd [2] OCTET STRING OPTIONAL }
+ #
+ # PasswdModifyResponseValue ::= SEQUENCE {
+ # genPasswd [0] OCTET STRING OPTIONAL }
+ #
+ # Encoded request:
+ #
+ # 00\x02\x01\x02w+\x80\x171.3.6.1.4.1.4203.1.11.1\x81\x100\x0E\x81\x05old\x82\x05new
+ #
+ def password_modify(args)
+ dn = args[:dn]
+ raise ArgumentError, 'DN is required' if !dn || dn.empty?
+
+ ext_seq = [Net::LDAP::PasswdModifyOid.to_ber_contextspecific(0)]
+
+ unless args[:old_password].nil?
+ pwd_seq = [args[:old_password].to_ber(0x81)]
+ pwd_seq << args[:new_password].to_ber(0x82) unless args[:new_password].nil?
+ ext_seq << pwd_seq.to_ber_sequence.to_ber(0x81)
+ end
+
+ request = ext_seq.to_ber_appsequence(Net::LDAP::PDU::ExtendedRequest)
+
+ message_id = next_msgid
+
+ write(request, nil, message_id)
+ pdu = queued_read(message_id)
+
+ if !pdu || pdu.app_tag != Net::LDAP::PDU::ExtendedResponse
+ raise Net::LDAP::ResponseMissingError, "response missing or invalid"
+ end
+
+ pdu
+ end
+
#--
# TODO: need to support a time limit, in case the server fails to respond.
# Unlike other operation-methods in this class, we return a result hash
# rather than a simple result number. This is experimental, and eventually
# we'll want to do this with all the others. The point is to have access
# to the error message and the matched-DN returned by the server.
#++
def add(args)
add_dn = args[:dn] or raise Net::LDAP::EmptyDNError, "Unable to add empty DN"
add_attrs = []
- a = args[:attributes] and a.each { |k, v|
- add_attrs << [ k.to_s.to_ber, Array(v).map { |m| m.to_ber}.to_ber_set ].to_ber_sequence
- }
+ a = args[:attributes] and a.each do |k, v|
+ add_attrs << [k.to_s.to_ber, Array(v).map(&:to_ber).to_ber_set].to_ber_sequence
+ end
message_id = next_msgid
request = [add_dn.to_ber, add_attrs.to_ber_sequence].to_ber_appsequence(Net::LDAP::PDU::AddRequest)
write(request, nil, message_id)
@@ -604,7 +658,36 @@
if !pdu || pdu.app_tag != Net::LDAP::PDU::DeleteResponse
raise Net::LDAP::ResponseMissingOrInvalidError, "response missing or invalid"
end
pdu
+ end
+
+ # Internal: Returns a Socket like object used internally to communicate with
+ # LDAP server.
+ #
+ # Typically a TCPSocket, but can be a OpenSSL::SSL::SSLSocket
+ def socket
+ return @conn if defined? @conn
+
+ # First refactoring uses the existing methods open_connection and
+ # prepare_socket to set @conn. Next cleanup would centralize connection
+ # handling here.
+ if @server[:socket]
+ prepare_socket(@server)
+ else
+ @server[:hosts] = [[@server[:host], @server[:port]]] if @server[:hosts].nil?
+ open_connection(@server)
+ end
+
+ @conn
+ end
+
+ private
+
+ # Wrap around Socket.tcp to normalize with other Socket initializers
+ class DefaultSocket
+ def self.new(host, port, socket_opts = {})
+ Socket.tcp(host, port, socket_opts)
+ end
end
end # class Connection