treequel 1.0.1 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
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: