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