treequel 1.0.1 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. data/ChangeLog +176 -14
  2. data/LICENSE +1 -1
  3. data/Rakefile +61 -45
  4. data/Rakefile.local +20 -0
  5. data/bin/treequel +502 -269
  6. data/examples/ldap-rack-auth.rb +2 -0
  7. data/lib/treequel.rb +221 -18
  8. data/lib/treequel/branch.rb +410 -201
  9. data/lib/treequel/branchcollection.rb +25 -13
  10. data/lib/treequel/branchset.rb +42 -40
  11. data/lib/treequel/constants.rb +233 -3
  12. data/lib/treequel/control.rb +95 -0
  13. data/lib/treequel/controls/contentsync.rb +138 -0
  14. data/lib/treequel/controls/pagedresults.rb +162 -0
  15. data/lib/treequel/controls/sortedresults.rb +216 -0
  16. data/lib/treequel/directory.rb +212 -65
  17. data/lib/treequel/exceptions.rb +11 -12
  18. data/lib/treequel/filter.rb +1 -12
  19. data/lib/treequel/mixins.rb +83 -47
  20. data/lib/treequel/monkeypatches.rb +29 -0
  21. data/lib/treequel/schema.rb +23 -19
  22. data/lib/treequel/schema/attributetype.rb +33 -3
  23. data/lib/treequel/schema/ldapsyntax.rb +0 -11
  24. data/lib/treequel/schema/matchingrule.rb +0 -11
  25. data/lib/treequel/schema/matchingruleuse.rb +0 -11
  26. data/lib/treequel/schema/objectclass.rb +36 -10
  27. data/lib/treequel/schema/table.rb +159 -0
  28. data/lib/treequel/sequel_integration.rb +7 -7
  29. data/lib/treequel/utils.rb +4 -66
  30. data/rake/documentation.rb +89 -0
  31. data/rake/helpers.rb +375 -307
  32. data/rake/hg.rb +16 -2
  33. data/rake/manual.rb +11 -6
  34. data/rake/packaging.rb +20 -35
  35. data/rake/publishing.rb +22 -62
  36. data/spec/lib/constants.rb +20 -0
  37. data/spec/lib/control_behavior.rb +44 -0
  38. data/spec/lib/matchers.rb +51 -0
  39. data/spec/treequel/branch_spec.rb +88 -29
  40. data/spec/treequel/branchcollection_spec.rb +24 -1
  41. data/spec/treequel/branchset_spec.rb +123 -51
  42. data/spec/treequel/control_spec.rb +48 -0
  43. data/spec/treequel/controls/contentsync_spec.rb +38 -0
  44. data/spec/treequel/controls/pagedresults_spec.rb +138 -0
  45. data/spec/treequel/controls/sortedresults_spec.rb +171 -0
  46. data/spec/treequel/directory_spec.rb +186 -16
  47. data/spec/treequel/mixins_spec.rb +42 -3
  48. data/spec/treequel/schema/attributetype_spec.rb +22 -20
  49. data/spec/treequel/schema/objectclass_spec.rb +67 -46
  50. data/spec/treequel/schema/table_spec.rb +134 -0
  51. data/spec/treequel_spec.rb +277 -15
  52. metadata +89 -108
  53. data/bin/treequel.orig +0 -963
  54. data/examples/ldap-monitor.rb +0 -143
  55. data/examples/ldap-monitor/public/css/master.css +0 -328
  56. data/examples/ldap-monitor/public/images/card_small.png +0 -0
  57. data/examples/ldap-monitor/public/images/chain_small.png +0 -0
  58. data/examples/ldap-monitor/public/images/globe_small.png +0 -0
  59. data/examples/ldap-monitor/public/images/globe_small_green.png +0 -0
  60. data/examples/ldap-monitor/public/images/plug.png +0 -0
  61. data/examples/ldap-monitor/public/images/shadows/large-30-down.png +0 -0
  62. data/examples/ldap-monitor/public/images/tick.png +0 -0
  63. data/examples/ldap-monitor/public/images/tick_circle.png +0 -0
  64. data/examples/ldap-monitor/public/images/treequel-favicon.png +0 -0
  65. data/examples/ldap-monitor/views/backends.erb +0 -41
  66. data/examples/ldap-monitor/views/connections.erb +0 -74
  67. data/examples/ldap-monitor/views/databases.erb +0 -39
  68. data/examples/ldap-monitor/views/dump_subsystem.erb +0 -14
  69. data/examples/ldap-monitor/views/index.erb +0 -14
  70. data/examples/ldap-monitor/views/layout.erb +0 -35
  71. data/examples/ldap-monitor/views/listeners.erb +0 -30
  72. data/rake/rdoc.rb +0 -30
  73. data/rake/win32.rb +0 -190
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ BEGIN {
4
+ require 'pathname'
5
+ basedir = Pathname.new( __FILE__ ).dirname.parent.parent
6
+
7
+ libdir = basedir + "lib"
8
+
9
+ $LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
10
+ }
11
+
12
+ require 'spec'
13
+ require 'spec/lib/constants'
14
+ require 'spec/lib/helpers'
15
+ require 'spec/lib/control_behavior'
16
+
17
+ require 'treequel'
18
+ require 'treequel/branchset'
19
+ require 'treequel/controls/sortedresults'
20
+
21
+ include Treequel::TestConstants
22
+ include Treequel::Constants
23
+
24
+ #####################################################################
25
+ ### C O N T E X T S
26
+ #####################################################################
27
+ describe Treequel::SortedResultsControl do
28
+ include Treequel::SpecHelpers
29
+
30
+ before( :all ) do
31
+ setup_logging( :fatal )
32
+ end
33
+
34
+ before( :each ) do
35
+ # Used by the shared behavior
36
+ @control = Treequel::SortedResultsControl
37
+ @branch = mock( "Branch", :dn => 'cn=example,dc=acme,dc=com' )
38
+ @directory = mock( "Directory" )
39
+
40
+ @branch.stub!( :directory ).and_return( @directory )
41
+ @directory.stub!( :registered_controls ).and_return([ Treequel::SortedResultsControl ])
42
+ @branchset = Treequel::Branchset.new( @branch )
43
+
44
+ end
45
+
46
+ after( :all ) do
47
+ reset_logging()
48
+ end
49
+
50
+
51
+ it_should_behave_like "A Treequel::Control"
52
+
53
+
54
+ it "adds a sort_order_criteria attribute to extended branchsets" do
55
+ @branchset.should respond_to( :sort_order_criteria )
56
+ end
57
+
58
+
59
+ it "can add the listed attributes as ascending sort order criteria via an #order mutator " +
60
+ "with Symbol attribute names" do
61
+ criteria = @branchset.order( :attr1, :attr2 ).sort_order_criteria
62
+ criteria.should have( 2 ).members
63
+
64
+ criteria[0].type.should == 'attr1'
65
+ criteria[0].ordering_rule.should == nil
66
+ criteria[0].reverse_order.should be_false()
67
+
68
+ criteria[1].type.should == 'attr2'
69
+ criteria[1].ordering_rule.should == nil
70
+ criteria[1].reverse_order.should be_false()
71
+ end
72
+
73
+ it "can add the listed attributes as descending sort order criteria via an #order mutator " +
74
+ "with Sequel::SQL::Expressions" do
75
+ pending "requires the 'sequel' library" unless Sequel.const_defined?( :Model )
76
+ criteria = @branchset.order( :attr1.desc ).sort_order_criteria
77
+
78
+ criteria.should have( 1 ).member
79
+ criteria[0].type.should == 'attr1'
80
+ criteria[0].ordering_rule.should == nil
81
+ criteria[0].reverse_order.should be_true()
82
+ end
83
+
84
+ it "can remove existing sort order criteria via the #order mutator with no arguments" do
85
+ ordered_branchset = @branchset.order( :attr1 )
86
+ ordered_branchset.order().sort_order_criteria.should be_empty()
87
+ end
88
+
89
+ it "can remove any existing sort order criteria via an #unordered mutator" do
90
+ ordered_branchset = @branchset.order( :attr1 )
91
+ ordered_branchset.unordered.sort_order_criteria.should be_empty()
92
+ end
93
+
94
+ it "can remove any existing sort order criteria from the receiver via the #unordered! " +
95
+ "imperative method" do
96
+ ordered_branchset = @branchset.order( :attr1 )
97
+ ordered_branchset.unordered!
98
+ ordered_branchset.sort_order_criteria.should be_empty()
99
+ end
100
+
101
+ it "injects the correct server-control structure into the search when iterating" do
102
+ oid = Treequel::SortedResultsControl::OID
103
+ expected_asn1_string = "0\x060\x04\x04\x02cn"
104
+ expected_control = LDAP::Control.new( oid, expected_asn1_string, true )
105
+
106
+ resultbranch = mock( "Sorted result branch" )
107
+ resultcontrol = mock( "Sorted result control" )
108
+
109
+ @branch.should_receive( :search ).with( :subtree,
110
+ instance_of(Treequel::Filter),
111
+ {
112
+ :limit => 0,
113
+ :selectattrs => [],
114
+ :timeout => 0,
115
+ :server_controls => [ expected_control ],
116
+ :client_controls => []
117
+ }
118
+ ).and_yield( resultbranch )
119
+
120
+ resultbranch.should_receive( :controls ).and_return([ resultcontrol ])
121
+ resultcontrol.should_receive( :oid ).
122
+ and_return( Treequel::SortedResultsControl::RESPONSE_OID )
123
+ resultcontrol.should_receive( :decode ).
124
+ and_return([ 0, :ignored ]) # 0 == Success
125
+
126
+ @branchset.order( :cn ).each do |*args|
127
+ args.should == [ resultbranch ]
128
+ end
129
+ end
130
+
131
+ it "raises an exception if the server returned an error in the response control" do
132
+ resultbranch = mock( "Sorted result branch" )
133
+ resultcontrol = mock( "Sorted result control" )
134
+
135
+ @branch.should_receive( :search ).and_yield( resultbranch )
136
+
137
+ resultbranch.should_receive( :controls ).and_return([ resultcontrol ])
138
+ resultcontrol.should_receive( :oid ).
139
+ and_return( Treequel::SortedResultsControl::RESPONSE_OID )
140
+ resultcontrol.should_receive( :decode ).
141
+ and_return([ 16, :ignored ]) # 16 == 'No such attribute'
142
+
143
+ expect {
144
+ @branchset.order( :cn ).each {}
145
+ }.to raise_exception( Treequel::ControlError, /no such attribute/i )
146
+ end
147
+
148
+ it "doesn't add a sort control if no sort order criteria have been set" do
149
+ resultbranch = mock( "Result branch" )
150
+
151
+ @branch.should_receive( :search ).with( :subtree,
152
+ instance_of(Treequel::Filter),
153
+ {
154
+ :limit => 0,
155
+ :selectattrs => [],
156
+ :timeout => 0,
157
+ :server_controls => [],
158
+ :client_controls => []
159
+ }
160
+ ).and_yield( resultbranch )
161
+
162
+ resultbranch.should_receive( :controls ).and_return( [] )
163
+
164
+ @branchset.unordered.each do |*args|
165
+ args.should == [ resultbranch ]
166
+ end
167
+ end
168
+
169
+ end
170
+
171
+ # vim: set nosta noet ts=4 sw=4:
@@ -9,20 +9,13 @@ BEGIN {
9
9
  $LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
10
10
  }
11
11
 
12
- begin
13
- require 'spec'
14
- require 'spec/lib/constants'
15
- require 'spec/lib/helpers'
16
-
17
- require 'treequel/directory'
18
- require 'treequel/branch'
19
- rescue LoadError
20
- unless Object.const_defined?( :Gem )
21
- require 'rubygems'
22
- retry
23
- end
24
- raise
25
- end
12
+ require 'spec'
13
+ require 'spec/lib/constants'
14
+ require 'spec/lib/helpers'
15
+
16
+ require 'treequel/directory'
17
+ require 'treequel/branch'
18
+ require 'treequel/control'
26
19
 
27
20
 
28
21
  include Treequel::TestConstants
@@ -82,8 +75,8 @@ describe Treequel::Directory do
82
75
  and_return( conn )
83
76
  conn.should_receive( :bind ).with( TEST_BIND_DN, TEST_BIND_PASS )
84
77
 
85
- dir = Treequel::Directory.new( @options.merge( :bind_dn => TEST_BIND_DN, :pass => TEST_BIND_PASS ))
86
- dir.instance_variable_get( :@bound_as ).should == TEST_BIND_DN
78
+ dir = Treequel::Directory.new( @options.merge(:bind_dn => TEST_BIND_DN, :pass => TEST_BIND_PASS) )
79
+ dir.bound_user.should == TEST_BIND_DN
87
80
  end
88
81
 
89
82
  it "uses the first namingContext from the Root DSE if no base is specified" do
@@ -282,6 +275,48 @@ describe Treequel::Directory do
282
275
  end
283
276
 
284
277
 
278
+ it "returns branches with operational attributes enabled if the base is a branch with " +
279
+ "operational attributes enabled" do
280
+ base = TEST_PEOPLE_DN
281
+ filter = '(|(uid=jonlong)(uid=margento))'
282
+
283
+ branch = mock( "branch", :dn => TEST_PEOPLE_DN )
284
+ branch.should_receive( :respond_to? ).with( :include_operational_attrs? ).
285
+ at_least( :once ).
286
+ and_return( true )
287
+ branch.should_receive( :respond_to? ).with( :dn ).
288
+ and_return( true )
289
+ branch.stub!( :include_operational_attrs? ).and_return( true )
290
+
291
+ found_branch1 = stub( "entry1 branch" )
292
+ found_branch2 = stub( "entry2 branch" )
293
+
294
+ # Do the search
295
+ entries = [
296
+ { 'dn' => ["uid=jonlong,#{TEST_PEOPLE_DN}"] },
297
+ { 'dn' => ["uid=margento,#{TEST_PEOPLE_DN}"] },
298
+ ]
299
+ @conn.should_receive( :search_ext2 ).
300
+ with( base, LDAP::LDAP_SCOPE_BASE, filter, ['*'], false, nil, nil, 0, 0, 0, '', nil ).
301
+ and_return( entries )
302
+
303
+ # Turn found entries into Branch objects
304
+ Treequel::Branch.should_receive( :new_from_entry ).with( entries[0], @dir ).
305
+ and_return( found_branch1 )
306
+ found_branch1.should_receive( :include_operational_attrs= ).with( true )
307
+ Treequel::Branch.should_receive( :new_from_entry ).with( entries[1], @dir ).
308
+ and_return( found_branch2 )
309
+ found_branch2.should_receive( :include_operational_attrs= ).with( true )
310
+
311
+ results = []
312
+ @dir.search( branch, :base, filter ) do |branch|
313
+ results << branch
314
+ end
315
+
316
+ results.should == [ found_branch1, found_branch2 ]
317
+ end
318
+
319
+
285
320
  it "catches plain RuntimeErrors raised by #search2 and re-casts them as " +
286
321
  "more-interesting errors" do
287
322
  @conn.should_receive( :search_ext2 ).
@@ -406,6 +441,16 @@ describe Treequel::Directory do
406
441
  @dir.modify( branch, 'cn' => ['nomblywob'] )
407
442
  end
408
443
 
444
+ it "can modify the record corresponding to a Branch in the directory via LDAP::Mods" do
445
+ branch = mock( "branch" )
446
+ branch.should_receive( :dn ).at_least( :once ).and_return( :the_branches_dn )
447
+ delmod = LDAP::Mod.new( LDAP::LDAP_MOD_DELETE, 'displayName', ['georgina boots'] )
448
+
449
+ @conn.should_receive( :modify ).with( :the_branches_dn, [delmod] )
450
+
451
+ @dir.modify( branch, [delmod] )
452
+ end
453
+
409
454
  it "can delete the record corresponding to a Branch from the directory" do
410
455
  branch = mock( "branch" )
411
456
  branch.should_receive( :dn ).at_least( :once ).and_return( :the_branches_dn )
@@ -446,6 +491,38 @@ describe Treequel::Directory do
446
491
  end
447
492
 
448
493
 
494
+ it "doesn't include duplicates when smushing RDN attributes" do
495
+ newattrs = {
496
+ TEST_PERSON_DN_ATTR => [TEST_PERSON_DN_VALUE],
497
+ :cn => 'Chilly T',
498
+ :desc => 'Audi like Jetta',
499
+ :objectClass => :room,
500
+ }
501
+ rdn_attrs = {
502
+ TEST_PERSON_DN_ATTR => [TEST_PERSON_DN_VALUE]
503
+ }
504
+ addattrs = {
505
+ 'cn' => ['Chilly T'],
506
+ 'desc' => ['Audi like Jetta'],
507
+ 'objectClass' => ['room'],
508
+ TEST_PERSON_DN_ATTR => [TEST_PERSON_DN_VALUE],
509
+ }
510
+
511
+ branch = mock( "new person branch" )
512
+ branch.should_receive( :dn ).and_return( TEST_PERSON_DN )
513
+ branch.should_receive( :rdn_attributes ).at_least( :once ).and_return( rdn_attrs )
514
+
515
+ room_objectclass = stub( 'room objectClass', :structural? => true )
516
+ @schema.should_receive( :object_classes ).at_least( :once ).and_return({
517
+ :room => room_objectclass,
518
+ })
519
+
520
+ @conn.should_receive( :add ).with( TEST_PERSON_DN, addattrs )
521
+
522
+ @dir.create( branch, newattrs )
523
+ end
524
+
525
+
449
526
  it "can move a record to a new dn within the same branch" do
450
527
  @dir.stub!( :bound? ).and_return( false )
451
528
  branch = mock( "sibling branch obj" )
@@ -480,6 +557,99 @@ describe Treequel::Directory do
480
557
  @dir.convert_syntax_value( OIDS::BOOLEAN_SYNTAX, 'true' ).should == 'true'
481
558
  end
482
559
 
560
+
561
+ ### Controls support
562
+ describe "to a server that supports controls introspection" do
563
+ before( :each ) do
564
+ @control = Module.new { include Treequel::Control }
565
+ @conn.should_receive( :root_dse ).and_return( TEST_DSE )
566
+ end
567
+
568
+
569
+ it "allows one to fetch the list of supported controls as OIDs" do
570
+ @dir.supported_control_oids.should == TEST_DSE.first['supportedControl']
571
+ end
572
+
573
+ it "allows one to fetch the list of supported controls as control names" do
574
+ @dir.supported_controls.should == TEST_DSE.first['supportedControl'].
575
+ collect {|oid| Treequel::Constants::CONTROL_NAMES[oid] }
576
+ end
577
+
578
+ it "allows the registration of one or more Treequel::Control modules" do
579
+ @control.const_set( :OID, TEST_DSE.first['supportedControl'].first )
580
+ @dir.register_controls( @control )
581
+ @dir.registered_controls.should == [ @control ]
582
+ end
583
+
584
+ it "raises an exception if the directory doesn't support registered controls" do
585
+ @control.const_set( :OID, '8.6.7.5.309' )
586
+ expect {
587
+ @dir.register_controls( @control )
588
+ }.to raise_error( Treequel::UnsupportedControl, /not supported/i )
589
+
590
+ @dir.registered_controls.should == []
591
+ end
592
+
593
+ it "raises an exception if a registered control doesn't define an OID" do
594
+ expect {
595
+ @dir.register_controls( @control )
596
+ }.to raise_error( NotImplementedError, /doesn't define/i )
597
+ end
598
+ end
599
+
600
+
601
+ describe "to a server that supports extensions introspection" do
602
+ before( :each ) do
603
+ @conn.should_receive( :root_dse ).and_return( TEST_DSE )
604
+ end
605
+
606
+
607
+ it "allows one to fetch the list of supported extensions as OIDs" do
608
+ @dir.supported_extension_oids.should == TEST_DSE.first['supportedExtension']
609
+ end
610
+
611
+ it "allows one to fetch the list of supported extensions as extension names" do
612
+ @dir.supported_extensions.should == TEST_DSE.first['supportedExtension'].
613
+ collect {|oid| Treequel::Constants::EXTENSION_NAMES[oid] }
614
+ end
615
+
616
+ end
617
+
618
+
619
+ describe "to a server that supports features introspection" do
620
+ before( :each ) do
621
+ @conn.should_receive( :root_dse ).and_return( TEST_DSE )
622
+ end
623
+
624
+
625
+ it "allows one to fetch the list of supported features as OIDs" do
626
+ @dir.supported_feature_oids.should == TEST_DSE.first['supportedFeatures']
627
+ end
628
+
629
+ it "allows one to fetch the list of supported features as feature names" do
630
+ @dir.supported_features.should == TEST_DSE.first['supportedFeatures'].
631
+ collect {|oid| Treequel::Constants::FEATURE_NAMES[oid] }
632
+ end
633
+
634
+ end
635
+
636
+ describe "to a server that doesn't support features introspection" do
637
+ before( :each ) do
638
+ @conn.should_receive( :root_dse ).and_return( TEST_DSE )
639
+ end
640
+
641
+
642
+ it "allows one to fetch the list of supported features as OIDs" do
643
+ @dir.supported_feature_oids.should == TEST_DSE.first['supportedFeatures']
644
+ end
645
+
646
+ it "allows one to fetch the list of supported features as feature names" do
647
+ @dir.supported_features.should == TEST_DSE.first['supportedFeatures'].
648
+ collect {|oid| Treequel::Constants::FEATURE_NAMES[oid] }
649
+ end
650
+
651
+ end
652
+
483
653
  end
484
654
  end
485
655
 
@@ -152,6 +152,7 @@ describe Treequel, "mixin" do
152
152
  end
153
153
 
154
154
  describe Treequel::ArrayUtilities do
155
+
155
156
  it "includes a function for stringifying Array elements" do
156
157
  testarray = [:a, :b, :c, [:d, :e, [:f, :g]]]
157
158
 
@@ -174,10 +175,9 @@ describe Treequel, "mixin" do
174
175
  end
175
176
  end
176
177
 
177
-
178
178
  describe Treequel::AttributeDeclarations do
179
179
  before( :all ) do
180
- setup_logging( :debug )
180
+ setup_logging( :fatal )
181
181
  end
182
182
  after( :all ) do
183
183
  reset_logging()
@@ -216,7 +216,7 @@ describe Treequel, "mixin" do
216
216
  describe Treequel::Delegation do
217
217
 
218
218
  before( :all ) do
219
- setup_logging( :debug )
219
+ setup_logging( :fatal )
220
220
  end
221
221
  after( :all ) do
222
222
  reset_logging()
@@ -325,6 +325,45 @@ describe Treequel, "mixin" do
325
325
 
326
326
  end
327
327
 
328
+ describe Treequel::Normalization do
329
+
330
+ describe "key normalization" do
331
+ it "downcases" do
332
+ Treequel::Normalization.normalize_key( :logonTime ).should == :logontime
333
+ end
334
+
335
+ it "symbolifies" do
336
+ Treequel::Normalization.normalize_key( 'cn' ).should == :cn
337
+ end
338
+
339
+ it "strips invalid characters" do
340
+ Treequel::Normalization.normalize_key( 'given name' ).should == :givenname
341
+ end
342
+
343
+ it "converts hyphens to underscores" do
344
+ Treequel::Normalization.normalize_key( 'apple-nickname' ).should == :apple_nickname
345
+ end
346
+ end
347
+
348
+ describe "hash normalization" do
349
+ it "applies key-normalization to the keys of a hash" do
350
+ hash = {
351
+ :logonTime => 'a logon time',
352
+ 'cn' => 'a common name',
353
+ 'given name' => 'a given name',
354
+ 'apple-nickname' => 'a nickname',
355
+ }
356
+
357
+ Treequel::Normalization.normalize_hash( hash ).should == {
358
+ :logontime => 'a logon time',
359
+ :cn => 'a common name',
360
+ :givenname => 'a given name',
361
+ :apple_nickname => 'a nickname',
362
+ }
363
+ end
364
+ end
365
+ end
366
+
328
367
  end
329
368
 
330
369
  # vim: set nosta noet ts=4 sw=4: