Class: Net::LDAP::Connection
- Inherits:
-
Object
- Object
- Net::LDAP::Connection
- Defined in:
- lib/net/ldap.rb
Overview
This is a private class used internally by the library. It should not be called by user code.
Constant Summary collapse
- LdapVersion =
:nodoc:
3
Instance Method Summary collapse
-
#add(args) ⇒ Object
– add TODO, need to support a time limit, in case the server fails to respond.
-
#bind(auth) ⇒ Object
– bind.
-
#close ⇒ Object
– close This is provided as a convenience method to make sure a connection object gets closed without waiting for a GC to happen.
-
#delete(args) ⇒ Object
– delete TODO, need to support a time limit, in case the server fails to respond.
-
#initialize(server) {|_self| ... } ⇒ Connection
constructor
– initialize.
-
#modify(args) ⇒ Object
– modify TODO, need to support a time limit, in case the server fails to respond.
-
#next_msgid ⇒ Object
– next_msgid.
-
#rename(args) ⇒ Object
– rename TODO, need to support a time limit, in case the server fails to respond.
-
#search(args = {}) ⇒ Object
– search Alternate implementation, this yields each search entry to the caller as it are received.
-
#setup_encryption(args) ⇒ Object
– Helper method called only from new, and only after we have a successfully-opened Depending on the received arguments, we establish SSL, potentially replacing the value of @conn accordingly.
Constructor Details
#initialize(server) {|_self| ... } ⇒ Connection
– initialize
1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 |
# File 'lib/net/ldap.rb', line 1017 def initialize server begin @conn = TCPsocket.new( server[:host], server[:port] ) rescue raise LdapError.new( "no connection to server" ) end if server[:encryption] setup_encryption server[:encryption] end yield self if block_given? end |
Instance Method Details
#add(args) ⇒ Object
– add TODO, need to support a time limit, in case the server fails to respond.
1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 |
# File 'lib/net/ldap.rb', line 1255 def add args add_dn = args[:dn] or raise LdapError.new("Unable to add empty DN") add_attrs = [] a = args[:attributes] and a.each {|k,v| add_attrs << [ k.to_s.to_ber, v.to_a.map {|m| m.to_ber}.to_ber_set ].to_ber_sequence } request = [add_dn.to_ber, add_attrs.to_ber_sequence].to_ber_appsequence(8) pkt = [next_msgid.to_ber, request].to_ber_sequence @conn.write pkt (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 9) or raise LdapError.new( "response missing or invalid" ) pdu.result_code end |
#bind(auth) ⇒ Object
– bind
1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 |
# File 'lib/net/ldap.rb', line 1090 def bind auth user,psw = case auth[:method] when :anonymous ["",""] when :simple [auth[:username] || auth[:dn], auth[:password]] end raise LdapError.new( "invalid binding information" ) unless (user && psw) msgid = next_msgid.to_ber request = [LdapVersion.to_ber, user.to_ber, psw.to_ber_contextspecific(0)].to_ber_appsequence(0) request_pkt = [msgid, request].to_ber_sequence @conn.write request_pkt (be = @conn.read_ber(AsnSyntax) and pdu = Net::LdapPdu.new( be )) or raise LdapError.new( "no bind result" ) pdu.result_code end |
#close ⇒ Object
– close This is provided as a convenience method to make sure a connection object gets closed without waiting for a GC to happen. Clients shouldn’t have to call it, but perhaps it will come in handy someday.
1073 1074 1075 1076 |
# File 'lib/net/ldap.rb', line 1073 def close @conn.close @conn = nil end |
#delete(args) ⇒ Object
– delete TODO, need to support a time limit, in case the server fails to respond.
1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 |
# File 'lib/net/ldap.rb', line 1293 def delete args dn = args[:dn] or raise "Unable to delete empty DN" request = dn.to_s.to_ber_application_string(10) pkt = [next_msgid.to_ber, request].to_ber_sequence @conn.write pkt (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 11) or raise LdapError.new( "response missing or invalid" ) pdu.result_code end |
#modify(args) ⇒ Object
– modify TODO, need to support a time limit, in case the server fails to respond. TODO!!! We’re throwing an exception here on empty DN. Should return a proper error instead, probaby from farther up the chain. TODO!!! If the user specifies a bogus opcode, we’ll throw a confusing error here (“to_ber_enumerated is not defined on nil”).
1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 |
# File 'lib/net/ldap.rb', line 1232 def modify args modify_dn = args[:dn] or raise "Unable to modify empty DN" modify_ops = [] a = args[:operations] and a.each {|op, attr, values| # TODO, fix the following line, which gives a bogus error # if the opcode is invalid. op_1 = {:add => 0, :delete => 1, :replace => 2} [op.to_sym].to_ber_enumerated modify_ops << [op_1, [attr.to_s.to_ber, values.to_a.map {|v| v.to_ber}.to_ber_set].to_ber_sequence].to_ber_sequence } request = [modify_dn.to_ber, modify_ops.to_ber_sequence].to_ber_appsequence(6) pkt = [next_msgid.to_ber, request].to_ber_sequence @conn.write pkt (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 7) or raise LdapError.new( "response missing or invalid" ) pdu.result_code end |
#next_msgid ⇒ Object
– next_msgid
1081 1082 1083 1084 |
# File 'lib/net/ldap.rb', line 1081 def next_msgid @msgid ||= 0 @msgid += 1 end |
#rename(args) ⇒ Object
– rename TODO, need to support a time limit, in case the server fails to respond.
1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 |
# File 'lib/net/ldap.rb', line 1275 def rename args old_dn = args[:olddn] or raise "Unable to rename empty DN" new_rdn = args[:newrdn] or raise "Unable to rename to empty RDN" delete_attrs = args[:delete_attributes] ? true : false request = [old_dn.to_ber, new_rdn.to_ber, delete_attrs.to_ber].to_ber_appsequence(12) pkt = [next_msgid.to_ber, request].to_ber_sequence @conn.write pkt (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 13) or raise LdapError.new( "response missing or invalid" ) pdu.result_code end |
#search(args = {}) ⇒ Object
– search Alternate implementation, this yields each search entry to the caller as it are received. TODO, certain search parameters are hardcoded. TODO, if we mis-parse the server results or the results are wrong, we can block forever. That’s because we keep reading results until we get a type-5 packet, which might never come. We need to support the time-limit in the protocol. – WARNING: this code substantially recapitulates the searchx method.
02May06: Well, I added support for RFC-2696-style paged searches. This is used on all queries because the extension is marked non-critical. As far as I know, only A/D uses this, but it’s required for A/D. Otherwise you won’t get more than 1000 results back from a query. This implementation is kindof clunky and should probably be refactored. Also, is it my imagination, or are A/Ds the slowest directory servers ever???
1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 |
# File 'lib/net/ldap.rb', line 1126 def search args = {} search_filter = (args && args[:filter]) || Filter.eq( "objectclass", "*" ) search_filter = Filter.construct(search_filter) if search_filter.is_a?(String) search_base = (args && args[:base]) || "dc=example,dc=com" search_attributes = ((args && args[:attributes]) || []).map {|attr| attr.to_s.to_ber} return_referrals = args && args[:return_referrals] == true attributes_only = (args and args[:attributes_only] == true) scope = args[:scope] || Net::LDAP::SearchScope_WholeSubtree raise LdapError.new( "invalid search scope" ) unless SearchScopes.include?(scope) # 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 is easily visible # by running slapd in debug mode. Go figure. = [126, ""] result_code = 0 loop { # should collect this into a private helper to clarify the structure request = [ search_base.to_ber, scope.to_ber_enumerated, 0.to_ber_enumerated, 0.to_ber, 0.to_ber, attributes_only.to_ber, search_filter.to_ber, search_attributes.to_ber_sequence ].to_ber_appsequence(3) controls = [ [ LdapControls::PagedResults.to_ber, false.to_ber, # criticality MUST be false to interoperate with normal LDAPs. .map{|v| v.to_ber}.to_ber_sequence.to_s.to_ber ].to_ber_sequence ].to_ber_contextspecific(0) pkt = [next_msgid.to_ber, request, controls].to_ber_sequence @conn.write pkt result_code = 0 controls = [] while (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) case pdu.app_tag when 4 # search-data yield( pdu.search_entry ) if block_given? when 19 # search-referral if return_referrals if block_given? se = Net::LDAP::Entry.new se[:search_referrals] = (pdu.search_referrals || []) yield se end end #p pdu.referrals when 5 # search-result result_code = pdu.result_code controls = pdu.result_controls break else raise LdapError.new( "invalid response-type in search: #{pdu.app_tag}" ) end end # When we get here, we have seen a type-5 response. # If there is no error AND there is an RFC-2696 cookie, # then query again for the next page of results. # If not, we're done. # Don't screw this up or we'll break every search we do. more_pages = false if result_code == 0 and controls controls.each do |c| if c.oid == LdapControls::PagedResults more_pages = false # just in case some bogus server sends us >1 of these. if c.value and c.value.length > 0 = c.value.read_ber[1] if and .length > 0 [1] = more_pages = true end end end end end break unless more_pages } # loop result_code end |
#setup_encryption(args) ⇒ Object
– Helper method called only from new, and only after we have a successfully-opened Depending on the received arguments, we establish SSL, potentially replacing the value of @conn accordingly. Don’t generate any errors here if no encryption is requested. DO raise LdapError objects if encryption is requested and we have trouble setting it up. That includes if OpenSSL is not set up on the machine. (Question: how does the Ruby OpenSSL wrapper react in that case?) DO NOT filter exceptions raised by the OpenSSL library. Let them pass back to the user. That should make it easier for us to debug the problem reports. Presumably (hopefully?) that will also produce recognizable errors if someone tries to use this on a machine without OpenSSL.
The simple_tls method is intended as the simplest, stupidest, easiest solution for people who want nothing more than encrypted comms with the LDAP server. It doesn’t do any server-cert validation and requires nothing in the way of key files and root-cert files, etc etc. OBSERVE: WE REPLACE the value of @conn, which is presumed to be a connected TCPsocket object.
1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 |
# File 'lib/net/ldap.rb', line 1053 def setup_encryption args case args[:method] when :simple_tls raise LdapError.new("openssl unavailable") unless $net_ldap_openssl_available ctx = OpenSSL::SSL::SSLContext.new @conn = OpenSSL::SSL::SSLSocket.new(@conn, ctx) @conn.connect @conn.sync_close = true # additional branches requiring server validation and peer certs, etc. go here. else raise LdapError.new( "unsupported encryption method #{args[:method]}" ) end end |