lib/net/ldap.rb in net-ldap-0.13.0 vs lib/net/ldap.rb in net-ldap-0.14.0

- old
+ new

@@ -262,18 +262,18 @@ include Net::LDAP::Instrumentation SearchScope_BaseObject = 0 SearchScope_SingleLevel = 1 SearchScope_WholeSubtree = 2 - SearchScopes = [ SearchScope_BaseObject, SearchScope_SingleLevel, - SearchScope_WholeSubtree ] + SearchScopes = [SearchScope_BaseObject, SearchScope_SingleLevel, + SearchScope_WholeSubtree] DerefAliases_Never = 0 DerefAliases_Search = 1 DerefAliases_Find = 2 DerefAliases_Always = 3 - DerefAliasesArray = [ DerefAliases_Never, DerefAliases_Search, DerefAliases_Find, DerefAliases_Always ] + DerefAliasesArray = [DerefAliases_Never, DerefAliases_Search, DerefAliases_Find, DerefAliases_Always] primitive = { 2 => :null } # UnbindRequest body constructed = { 0 => :array, # BindRequest 1 => :array, # BindResponse @@ -321,20 +321,28 @@ context_specific = { :primitive => primitive, :constructed => constructed, } + universal = { + constructed: { + 107 => :array, #ExtendedResponse (PasswdModifyResponseValue) + }, + } + AsnSyntax = Net::BER.compile_syntax(:application => application, + :universal => universal, :context_specific => context_specific) DefaultHost = "127.0.0.1" DefaultPort = 389 DefaultAuth = { :method => :anonymous } DefaultTreebase = "dc=com" DefaultForceNoPage = false - StartTlsOid = "1.3.6.1.4.1.1466.20037" + StartTlsOid = '1.3.6.1.4.1.1466.20037' + PasswdModifyOid = '1.3.6.1.4.1.4203.1.11.1' # https://tools.ietf.org/html/rfc4511#section-4.1.9 # https://tools.ietf.org/html/rfc4511#appendix-A ResultCodeSuccess = 0 ResultCodeOperationsError = 1 @@ -379,18 +387,18 @@ ResultCodesNonError = [ ResultCodeSuccess, ResultCodeCompareFalse, ResultCodeCompareTrue, ResultCodeReferral, - ResultCodeSaslBindInProgress + ResultCodeSaslBindInProgress, ] # nonstandard list of "successful" result codes for searches ResultCodesSearchSuccess = [ ResultCodeSuccess, ResultCodeTimeLimitExceeded, - ResultCodeSizeLimitExceeded + ResultCodeSizeLimitExceeded, ] # map of result code to human message ResultStrings = { ResultCodeSuccess => "Success", @@ -428,11 +436,11 @@ ResultCodeNotAllowedOnNonLeaf => "Not Allowed On Non-Leaf", ResultCodeNotAllowedOnRDN => "Not Allowed On RDN", ResultCodeEntryAlreadyExists => "Entry Already Exists", ResultCodeObjectClassModsProhibited => "ObjectClass Modifications Prohibited", ResultCodeAffectsMultipleDSAs => "Affects Multiple DSAs", - ResultCodeOther => "Other" + ResultCodeOther => "Other", } 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" @@ -529,11 +537,11 @@ @hosts = args[:hosts] @verbose = false # Make this configurable with a switch on the class. @auth = args[:auth] || DefaultAuth @base = args[:base] || DefaultTreebase @force_no_page = args[:force_no_page] || DefaultForceNoPage - @encryption = args[:encryption] # may be nil + @encryption = normalize_encryption(args[:encryption]) # may be nil @connect_timeout = args[:connect_timeout] if pr = @auth[:password] and pr.respond_to?(:call) @auth[:password] = pr.call end @@ -581,11 +589,11 @@ def authenticate(username, password) password = password.call if password.respond_to?(:call) @auth = { :method => :simple, :username => username, - :password => password + :password => password, } end alias_method :auth, :authenticate # Convenience method to specify encryption characteristics for connections @@ -599,17 +607,11 @@ # This method is deprecated. # def encryption(args) warn "Deprecation warning: please give :encryption option as a Hash to Net::LDAP.new" return if args.nil? - return @encryption = args if args.is_a? Hash - - case method = args.to_sym - when :simple_tls, :start_tls - args = { :method => method, :tls_options => {} } - end - @encryption = args + @encryption = normalize_encryption(args) end # #open takes the same parameters as #new. #open makes a network # connection to the LDAP server and then passes a newly-created Net::LDAP # object to the caller-supplied block. Within the block, you can call any @@ -649,12 +651,15 @@ # Modified the implementation, 20Mar07. We might get a hash of LDAP # response codes instead of a simple numeric code. #++ def get_operation_result result = @result - result = result.result if result.is_a?(Net::LDAP::PDU) os = OpenStruct.new + if result.is_a?(Net::LDAP::PDU) + os.extended_response = result.extended_response + result = result.result + end if result.is_a?(Hash) # We might get a hash of LDAP response codes instead of a simple # numeric code. os.code = (result[:resultCode] || "").to_i os.error_message = result[:errorMessage] @@ -762,14 +767,14 @@ return_result_set = args[:return_result] != false result_set = return_result_set ? [] : nil instrument "search.net_ldap", args do |payload| @result = use_connection(args) do |conn| - conn.search(args) { |entry| + conn.search(args) do |entry| result_set << entry if result_set yield entry if block_given? - } + end end if return_result_set unless @result.nil? if ResultCodesSearchSuccess.include?(@result.result_code) @@ -904,19 +909,19 @@ # else # puts "Authentication FAILED." # end def bind_as(args = {}) result = false - open { |me| + open do |me| rs = search args if rs and rs.first and dn = rs.first.dn password = args[:password] password = password.call if password.respond_to?(:call) result = rs if bind(:method => :simple, :username => dn, :password => password) end - } + end result end # Adds a new entry to the remote LDAP server. # Supported arguments: @@ -1039,10 +1044,48 @@ end @result.success? end end + # Password Modify + # + # Change existing password: + # + # dn = 'uid=modify-password-user1,ou=People,dc=rubyldap,dc=com' + # auth = { + # method: :simple, + # username: dn, + # password: 'passworD1' + # } + # ldap.password_modify(dn: dn, + # auth: auth, + # old_password: 'passworD1', + # new_password: 'passworD2') + # + # Or get the LDAP server to generate a password for you: + # + # dn = 'uid=modify-password-user1,ou=People,dc=rubyldap,dc=com' + # auth = { + # method: :simple, + # username: dn, + # password: 'passworD1' + # } + # ldap.password_modify(dn: dn, + # auth: auth, + # old_password: 'passworD1') + # + # ldap.get_operation_result.extended_response[0][0] #=> 'VtcgGf/G' + # + def password_modify(args) + instrument "modify_password.net_ldap", args do |payload| + @result = use_connection(args) do |conn| + conn.password_modify(args) + end + @result.success? + end + end + # Add a value to an attribute. Takes the full DN of the entry to modify, # the name (Symbol or String) of the attribute, and the value (String or # Array). If the attribute does not exist (and there are no schema # violations), #add_attribute will create it with the caller-specified # values. If the attribute already exists (and there are no schema @@ -1157,11 +1200,11 @@ :supportedCapabilities, :supportedControl, :supportedExtension, :supportedFeatures, :supportedLdapVersion, - :supportedSASLMechanisms + :supportedSASLMechanisms, ]) (rs and rs.first) or Net::LDAP::Entry.new end # Return the root Subschema record from the LDAP server as a @@ -1224,10 +1267,15 @@ inspected = super inspected.gsub! @auth[:password], "*******" if @auth[:password] inspected end + # Internal: Set @open_connection for testing + def connection=(connection) + @open_connection = connection + end + private # Yields an open connection if there is one, otherwise establishes a new # connection, binds, and yields it. If binding fails, it will return the # result from that, and :use_connection: will not yield at all. If not @@ -1249,20 +1297,37 @@ end end # Establish a new connection to the LDAP server def new_connection - Net::LDAP::Connection.new \ + connection = Net::LDAP::Connection.new \ :host => @host, :port => @port, :hosts => @hosts, :encryption => @encryption, :instrumentation_service => @instrumentation_service, :connect_timeout => @connect_timeout + + # Force connect to see if there's a connection error + connection.socket + connection rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, Net::LDAP::ConnectionRefusedError => e @result = { :resultCode => 52, - :errorMessage => ResultStrings[ResultCodeUnavailable] + :errorMessage => ResultStrings[ResultCodeUnavailable], } raise e end + + # Normalize encryption parameter the constructor accepts, expands a few + # convenience symbols into recognizable hashes + def normalize_encryption(args) + return if args.nil? + return args if args.is_a? Hash + + case method = args.to_sym + when :simple_tls, :start_tls + { :method => method, :tls_options => {} } + end + end + end # class LDAP