@@ -335,8 +335,11 @@ class LdapError < StandardError; end
335
335
68 => "Entry Already Exists"
336
336
}
337
337
338
- module LdapControls
339
- PagedResults = "1.2.840.113556.1.4.319" # Microsoft evil from RFC 2696
338
+ module LDAPControls
339
+ PAGED_RESULTS = "1.2.840.113556.1.4.319" # Microsoft evil from RFC 2696
340
+ SORT_REQUEST = "1.2.840.113556.1.4.473"
341
+ SORT_RESPONSE = "1.2.840.113556.1.4.474"
342
+ DELETE_TREE = "1.2.840.113556.1.4.805"
340
343
end
341
344
342
345
def self . result2string ( code ) #:nodoc:
@@ -629,11 +632,10 @@ def search(args = {})
629
632
yield entry if block_given?
630
633
}
631
634
else
632
- @result = 0
633
635
begin
634
636
conn = Net ::LDAP ::Connection . new ( :host => @host , :port => @port ,
635
637
:encryption => @encryption )
636
- if ( @result = conn . bind ( args [ :auth ] || @auth ) ) == 0
638
+ if ( @result = conn . bind ( args [ :auth ] || @auth ) ) . result_code == 0
637
639
@result = conn . search ( args ) { |entry |
638
640
result_set << entry if result_set
639
641
yield entry if block_given?
@@ -645,9 +647,9 @@ def search(args = {})
645
647
end
646
648
647
649
if return_result_set
648
- @result == 0 ? result_set : nil
650
+ ( ! @result . nil? && @result . result_code == 0 ) ? result_set : nil
649
651
else
650
- @result == 0
652
+ @result
651
653
end
652
654
end
653
655
@@ -721,7 +723,7 @@ def bind(auth = @auth)
721
723
end
722
724
end
723
725
724
- @result == 0
726
+ @result
725
727
end
726
728
727
729
# #bind_as is for testing authentication credentials.
@@ -816,14 +818,14 @@ def add(args)
816
818
begin
817
819
conn = Connection . new ( :host => @host , :port => @port ,
818
820
:encryption => @encryption )
819
- if ( @result = conn . bind ( args [ :auth ] || @auth ) ) == 0
821
+ if ( @result = conn . bind ( args [ :auth ] || @auth ) ) . result_code == 0
820
822
@result = conn . add ( args )
821
823
end
822
824
ensure
823
825
conn . close if conn
824
826
end
825
827
end
826
- @result == 0
828
+ @result
827
829
end
828
830
829
831
# Modifies the attribute values of a particular entry on the LDAP
@@ -914,14 +916,15 @@ def modify(args)
914
916
begin
915
917
conn = Connection . new ( :host => @host , :port => @port ,
916
918
:encryption => @encryption )
917
- if ( @result = conn . bind ( args [ :auth ] || @auth ) ) == 0
919
+ if ( @result = conn . bind ( args [ :auth ] || @auth ) ) . result_code == 0
918
920
@result = conn . modify ( args )
919
921
end
920
922
ensure
921
923
conn . close if conn
922
924
end
923
925
end
924
- @result == 0
926
+
927
+ @result
925
928
end
926
929
927
930
# Add a value to an attribute. Takes the full DN of the entry to modify,
@@ -985,14 +988,14 @@ def rename(args)
985
988
begin
986
989
conn = Connection . new ( :host => @host , :port => @port ,
987
990
:encryption => @encryption )
988
- if ( @result = conn . bind ( args [ :auth ] || @auth ) ) == 0
991
+ if ( @result = conn . bind ( args [ :auth ] || @auth ) ) . result_code == 0
989
992
@result = conn . rename ( args )
990
993
end
991
994
ensure
992
995
conn . close if conn
993
996
end
994
997
end
995
- @result == 0
998
+ @result
996
999
end
997
1000
alias_method :modify_rdn , :rename
998
1001
@@ -1013,16 +1016,29 @@ def delete(args)
1013
1016
begin
1014
1017
conn = Connection . new ( :host => @host , :port => @port ,
1015
1018
:encryption => @encryption )
1016
- if ( @result = conn . bind ( args [ :auth ] || @auth ) ) == 0
1019
+ if ( @result = conn . bind ( args [ :auth ] || @auth ) ) . result_code == 0
1017
1020
@result = conn . delete ( args )
1018
1021
end
1019
1022
ensure
1020
1023
conn . close
1021
1024
end
1022
1025
end
1023
- @result == 0
1026
+ @result
1024
1027
end
1025
1028
1029
+ # Delete an entry from the LDAP directory along with all subordinate entries.
1030
+ # the regular delete method will fail to delete an entry if it has subordinate
1031
+ # entries. This method sends an extra control code to tell the LDAP server
1032
+ # to do a tree delete. ('1.2.840.113556.1.4.805')
1033
+ #
1034
+ # Returns True or False to indicate whether the delete succeeded. Extended
1035
+ # status information is available by calling #get_operation_result.
1036
+ #
1037
+ # dn = "[email protected] , ou=people, dc=example, dc=com"
1038
+ # ldap.delete_tree :dn => dn
1039
+ def delete_tree ( args )
1040
+ delete ( args . merge ( :control_codes => [ [ Net ::LDAP ::LDAPControls ::DELETE_TREE , true ] ] ) )
1041
+ end
1026
1042
# This method is experimental and subject to change. Return the rootDSE
1027
1043
# record from the LDAP server as a Net::LDAP::Entry, or an empty Entry if
1028
1044
# the server doesn't return the record.
@@ -1093,7 +1109,7 @@ def search_subschema_entry
1093
1109
#++
1094
1110
def paged_searches_supported?
1095
1111
@server_caps ||= search_root_dse
1096
- @server_caps [ :supportedcontrol ] . include? ( Net ::LDAP ::LdapControls :: PagedResults )
1112
+ @server_caps [ :supportedcontrol ] . include? ( Net ::LDAP ::LDAPControls :: PAGED_RESULTS )
1097
1113
end
1098
1114
end # class LDAP
1099
1115
@@ -1237,7 +1253,7 @@ def bind_simple(auth)
1237
1253
1238
1254
( be = @conn . read_ber ( Net ::LDAP ::AsnSyntax ) and pdu = Net ::LDAP ::PDU . new ( be ) ) or raise Net ::LDAP ::LdapError , "no bind result"
1239
1255
1240
- pdu . result_code
1256
+ pdu
1241
1257
end
1242
1258
1243
1259
#--
@@ -1275,7 +1291,7 @@ def bind_sasl(auth)
1275
1291
@conn . write request_pkt
1276
1292
1277
1293
( be = @conn . read_ber ( Net ::LDAP ::AsnSyntax ) and pdu = Net ::LDAP ::PDU . new ( be ) ) or raise Net ::LDAP ::LdapError , "no bind result"
1278
- return pdu . result_code unless pdu . result_code == 14 # saslBindInProgress
1294
+ return pdu unless pdu . result_code == 14 # saslBindInProgress
1279
1295
raise Net ::LDAP ::LdapError , "sasl-challenge overflow" if ( ( n += 1 ) > MaxSaslChallenges )
1280
1296
1281
1297
cred = chall . call ( pdu . result_server_sasl_creds )
@@ -1315,6 +1331,35 @@ def bind_gss_spnego(auth)
1315
1331
end
1316
1332
private :bind_gss_spnego
1317
1333
1334
+
1335
+ #--
1336
+ # Allow the caller to specify a sort control
1337
+ #
1338
+ # The format of the sort control needs to be:
1339
+ #
1340
+ # :sort_control => ["cn"] # just a string
1341
+ # or
1342
+ # :sort_control => [["cn", "matchingRule", true]] #attribute, matchingRule, direction (true / false)
1343
+ # or
1344
+ # :sort_control => ["givenname","sn"] #multiple strings or arrays
1345
+ #
1346
+ def encode_sort_controls ( sort_definitions )
1347
+ return sort_definitions unless sort_definitions
1348
+
1349
+ sort_control_values = sort_definitions . map do |control |
1350
+ control = Array ( control ) # if there is only an attribute name as a string then infer the orderinrule and reverseorder
1351
+ control [ 0 ] = String ( control [ 0 ] ) . to_ber ,
1352
+ control [ 1 ] = String ( control [ 1 ] ) . to_ber ,
1353
+ control [ 2 ] = ( control [ 2 ] == true ) . to_ber
1354
+ control . to_ber_sequence
1355
+ end
1356
+ sort_control = [
1357
+ Net ::LDAP ::LDAPControls ::SORT_REQUEST . to_ber ,
1358
+ false . to_ber ,
1359
+ sort_control_values . to_ber_sequence . to_s . to_ber
1360
+ ] . to_ber_sequence
1361
+ end
1362
+
1318
1363
#--
1319
1364
# Alternate implementation, this yields each search entry to the caller as
1320
1365
# it are received.
@@ -1340,6 +1385,7 @@ def search(args = {})
1340
1385
scope = args [ :scope ] || Net ::LDAP ::SearchScope_WholeSubtree
1341
1386
raise Net ::LDAP ::LdapError , "invalid search scope" unless Net ::LDAP ::SearchScopes . include? ( scope )
1342
1387
1388
+ sort_control = encode_sort_controls ( args . fetch ( :sort_controls ) { false } )
1343
1389
# An interesting value for the size limit would be close to A/D's
1344
1390
# built-in page limit of 1000 records, but openLDAP newer than version
1345
1391
# 2.2.0 chokes on anything bigger than 126. You get a silent error that
@@ -1361,7 +1407,7 @@ def search(args = {})
1361
1407
# to do a root-DSE record search and not do a paged search if the LDAP
1362
1408
# doesn't support it. Yuck.
1363
1409
rfc2696_cookie = [ 126 , "" ]
1364
- result_code = 0
1410
+ result_pdu = nil
1365
1411
n_results = 0
1366
1412
1367
1413
loop {
@@ -1390,17 +1436,18 @@ def search(args = {})
1390
1436
controls = [ ]
1391
1437
controls <<
1392
1438
[
1393
- Net ::LDAP ::LdapControls :: PagedResults . to_ber ,
1439
+ Net ::LDAP ::LDAPControls :: PAGED_RESULTS . to_ber ,
1394
1440
# Criticality MUST be false to interoperate with normal LDAPs.
1395
1441
false . to_ber ,
1396
1442
rfc2696_cookie . map { |v | v . to_ber } . to_ber_sequence . to_s . to_ber
1397
1443
] . to_ber_sequence if paged_searches_supported
1444
+ controls << sort_control if sort_control
1398
1445
controls = controls . empty? ? nil : controls . to_ber_contextspecific ( 0 )
1399
1446
1400
1447
pkt = [ next_msgid . to_ber , request , controls ] . compact . to_ber_sequence
1401
1448
@conn . write pkt
1402
1449
1403
- result_code = 0
1450
+ result_pdu = nil
1404
1451
controls = [ ]
1405
1452
1406
1453
while ( be = @conn . read_ber ( Net ::LDAP ::AsnSyntax ) ) && ( pdu = Net ::LDAP ::PDU . new ( be ) )
@@ -1417,7 +1464,7 @@ def search(args = {})
1417
1464
end
1418
1465
end
1419
1466
when 5 # search-result
1420
- result_code = pdu . result_code
1467
+ result_pdu = pdu
1421
1468
controls = pdu . result_controls
1422
1469
if return_referrals && result_code == 10
1423
1470
if block_given?
@@ -1443,9 +1490,9 @@ def search(args = {})
1443
1490
# of type OCTET STRING, covered in the default syntax supported by
1444
1491
# read_ber, so I guess we're ok.
1445
1492
more_pages = false
1446
- if result_code == 0 and controls
1493
+ if result_pdu . result_code == 0 and controls
1447
1494
controls . each do |c |
1448
- if c . oid == Net ::LDAP ::LdapControls :: PagedResults
1495
+ if c . oid == Net ::LDAP ::LDAPControls :: PAGED_RESULTS
1449
1496
# just in case some bogus server sends us more than 1 of these.
1450
1497
more_pages = false
1451
1498
if c . value and c . value . length > 0
@@ -1462,7 +1509,7 @@ def search(args = {})
1462
1509
break unless more_pages
1463
1510
} # loop
1464
1511
1465
- result_code
1512
+ result_pdu || OpenStruct . new ( :status => :failure , : result_code => 1 , :message => "Invalid search" )
1466
1513
end
1467
1514
1468
1515
MODIFY_OPERATIONS = { #:nodoc:
@@ -1502,7 +1549,8 @@ def modify(args)
1502
1549
@conn . write pkt
1503
1550
1504
1551
( 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"
1505
- pdu . result_code
1552
+
1553
+ pdu
1506
1554
end
1507
1555
1508
1556
#--
@@ -1523,8 +1571,12 @@ def add(args)
1523
1571
pkt = [ next_msgid . to_ber , request ] . to_ber_sequence
1524
1572
@conn . write pkt
1525
1573
1526
- ( 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"
1527
- pdu . result_code
1574
+ ( be = @conn . read_ber ( Net ::LDAP ::AsnSyntax ) ) &&
1575
+ ( pdu = Net ::LDAP ::PDU . new ( be ) ) &&
1576
+ ( pdu . app_tag == 9 ) or
1577
+ raise Net ::LDAP ::LdapError , "response missing or invalid"
1578
+
1579
+ pdu
1528
1580
end
1529
1581
1530
1582
#--
@@ -1545,20 +1597,22 @@ def rename args
1545
1597
( be = @conn . read_ber ( Net ::LDAP ::AsnSyntax ) ) &&
1546
1598
( pdu = Net ::LDAP ::PDU . new ( be ) ) && ( pdu . app_tag == 13 ) or
1547
1599
raise Net ::LDAP ::LdapError . new ( "response missing or invalid" )
1548
- pdu . result_code
1600
+
1601
+ pdu
1549
1602
end
1550
1603
1551
1604
#--
1552
1605
# TODO, need to support a time limit, in case the server fails to respond.
1553
1606
#++
1554
1607
def delete ( args )
1555
1608
dn = args [ :dn ] or raise "Unable to delete empty DN"
1556
-
1609
+ controls = args . include? ( :control_codes ) ? args [ :control_codes ] . to_ber_control : nil #use nil so we can compact later
1557
1610
request = dn . to_s . to_ber_application_string ( 10 )
1558
- pkt = [ next_msgid . to_ber , request ] . to_ber_sequence
1611
+ pkt = [ next_msgid . to_ber , request , controls ] . compact . to_ber_sequence
1559
1612
@conn . write pkt
1560
1613
1561
1614
( 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"
1562
- pdu . result_code
1615
+
1616
+ pdu
1563
1617
end
1564
1618
end # class Connection
0 commit comments