ucb_ldap 2.0.0.pre1 → 2.0.0.pre3

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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/CHANGELOG +137 -135
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/{README → README.md} +82 -80
  7. data/Rakefile +38 -20
  8. data/lib/ucb_ldap.rb +238 -204
  9. data/lib/{ucb_ldap_address.rb → ucb_ldap/address.rb} +106 -106
  10. data/lib/{ucb_ldap_affiliation.rb → ucb_ldap/affiliation.rb} +16 -16
  11. data/lib/{ucb_ldap_entry.rb → ucb_ldap/entry.rb} +455 -448
  12. data/lib/{ucb_ldap_person_job_appointment.rb → ucb_ldap/job_appointment.rb} +77 -79
  13. data/lib/{ucb_ldap_namespace.rb → ucb_ldap/namespace.rb} +40 -50
  14. data/lib/{ucb_ldap_org.rb → ucb_ldap/org.rb} +427 -429
  15. data/lib/{ucb_ldap_person.rb → ucb_ldap/person.rb} +157 -148
  16. data/lib/{person → ucb_ldap/person}/affiliation_methods.rb +23 -22
  17. data/lib/ucb_ldap/person/common_attributes.rb +63 -0
  18. data/lib/{ucb_ldap_schema.rb → ucb_ldap/schema.rb} +28 -28
  19. data/lib/{ucb_ldap_schema_attribute.rb → ucb_ldap/schema_attribute.rb} +152 -153
  20. data/lib/{ucb_ldap_service.rb → ucb_ldap/service.rb} +17 -19
  21. data/lib/{ucb_ldap_student_term.rb → ucb_ldap/student_term.rb} +29 -31
  22. data/lib/ucb_ldap/version.rb +3 -0
  23. data/spec/rails_binds.yml +9 -0
  24. data/spec/spec_helper.rb +43 -0
  25. data/spec/ucb_ldap/address_spec.rb +54 -0
  26. data/spec/ucb_ldap/affiliation_spec.rb +85 -0
  27. data/spec/ucb_ldap/entry_spec.rb +241 -0
  28. data/spec/ucb_ldap/job_appointment_spec.rb +65 -0
  29. data/spec/ucb_ldap/namespace_spec.rb +72 -0
  30. data/spec/ucb_ldap/org_spec.rb +217 -0
  31. data/spec/ucb_ldap/person_spec.rb +225 -0
  32. data/spec/ucb_ldap/schema_attribute_spec.rb +122 -0
  33. data/spec/ucb_ldap/schema_spec.rb +104 -0
  34. data/spec/ucb_ldap/service_spec.rb +127 -0
  35. data/spec/ucb_ldap/student_term_spec.rb +121 -0
  36. data/spec/ucb_ldap_spec.rb +182 -0
  37. data/ucb_ldap.gemspec +20 -27
  38. metadata +113 -64
  39. data/Manifest +0 -23
  40. data/TODO +0 -2
  41. data/lib/person/adv_con_person.rb +0 -0
  42. data/lib/person/generic_attributes.rb +0 -68
  43. data/lib/ucb_ldap_exceptions.rb +0 -27
  44. data/version.yml +0 -1
@@ -1,79 +1,77 @@
1
-
2
- module UCB
3
- module LDAP
4
- # = UCB::LDAP::JobAppointment
5
- #
6
- # This class models a person's job appointment instance in the UCB LDAP directory.
7
- #
8
- # appts = JobAppontment.find_by_uid("1234") #=> [#<UCB::LDAP::JobAppointment: ...>, ...]
9
- #
10
- # JobAppointments are usually loaded through a Person instance:
11
- #
12
- # p = Person.find_by_uid("1234") #=> #<UCB::LDAP::Person: ...>
13
- # appts = p.job_appointments #=> [#<UCB::LDAP::JobAppointment: ...>, ...]
14
- #
15
- # == Note on Binds
16
- #
17
- # You must have a privileged bind and pass your credentials to UCB::LDAP.authenticate()
18
- # before performing your JobAppointment search.
19
- #
20
- class JobAppointment < Entry
21
- @entity_name = 'personJobAppointment'
22
-
23
- def cto_code
24
- berkeleyEduPersonJobApptCTOCode
25
- end
26
-
27
- def deptid
28
- berkeleyEduPersonJobApptDepartment
29
- end
30
-
31
- def record_number
32
- berkeleyEduPersonJobApptEmpRecNumber.to_i
33
- end
34
-
35
- def personnel_program_code
36
- berkeleyEduPersonJobApptPersPgmCode
37
- end
38
-
39
- def primary?
40
- berkeleyEduPersonJobApptPrimaryFlag
41
- end
42
-
43
- # Returns Employee Relation Code
44
- def erc_code
45
- berkeleyEduPersonJobApptRelationsCode
46
- end
47
-
48
- def represented?
49
- berkeleyEduPersonJobApptRepresentation != 'U'
50
- end
51
-
52
- def title_code
53
- berkeleyEduPersonJobApptTitleCode
54
- end
55
-
56
- def appointment_type
57
- berkeleyEduPersonJobApptType
58
- end
59
-
60
- # Returns +true+ if appointment is Without Salary
61
- def wos?
62
- berkeleyEduPersonJobApptWOS
63
- end
64
-
65
- class << self
66
- # Returns an Array of JobAppointment for <tt>uid</tt>, sorted by
67
- # record_number().
68
- # Returns an empty Array ([]) if nothing is found.
69
- #
70
- def find_by_uid(uid)
71
- base = "uid=#{uid},ou=people,dc=berkeley,dc=edu"
72
- filter = Net::LDAP::Filter.eq("objectclass", 'berkeleyEduPersonJobAppt')
73
- search(:base => base, :filter => filter).sort_by{|appt| appt.record_number}
74
- end
75
-
76
- end
77
- end
78
- end
79
- end
1
+ module UCB
2
+ module LDAP
3
+ # = UCB::LDAP::JobAppointment
4
+ #
5
+ # This class models a person's job appointment instance in the UCB LDAP directory.
6
+ #
7
+ # appts = JobAppontment.find_by_uid("1234") #=> [#<UCB::LDAP::JobAppointment: ...>, ...]
8
+ #
9
+ # JobAppointments are usually loaded through a Person instance:
10
+ #
11
+ # p = Person.find_by_uid("1234") #=> #<UCB::LDAP::Person: ...>
12
+ # appts = p.job_appointments #=> [#<UCB::LDAP::JobAppointment: ...>, ...]
13
+ #
14
+ # == Note on Binds
15
+ #
16
+ # You must have a privileged bind and pass your credentials to UCB::LDAP.authenticate()
17
+ # before performing your JobAppointment search.
18
+ #
19
+ class JobAppointment < Entry
20
+ @entity_name = 'personJobAppointment'
21
+
22
+ def cto_code
23
+ berkeleyEduPersonJobApptCTOCode
24
+ end
25
+
26
+ def deptid
27
+ berkeleyEduPersonJobApptDepartment
28
+ end
29
+
30
+ def record_number
31
+ berkeleyEduPersonJobApptEmpRecNumber.to_i
32
+ end
33
+
34
+ def personnel_program_code
35
+ berkeleyEduPersonJobApptPersPgmCode
36
+ end
37
+
38
+ def primary?
39
+ berkeleyEduPersonJobApptPrimaryFlag
40
+ end
41
+
42
+ # Returns Employee Relation Code
43
+ def erc_code
44
+ berkeleyEduPersonJobApptRelationsCode
45
+ end
46
+
47
+ def represented?
48
+ berkeleyEduPersonJobApptRepresentation != 'U'
49
+ end
50
+
51
+ def title_code
52
+ berkeleyEduPersonJobApptTitleCode
53
+ end
54
+
55
+ def appointment_type
56
+ berkeleyEduPersonJobApptType
57
+ end
58
+
59
+ # Returns +true+ if appointment is Without Salary
60
+ def wos?
61
+ berkeleyEduPersonJobApptWOS
62
+ end
63
+
64
+
65
+ # Returns an Array of JobAppointment for <tt>uid</tt>, sorted by
66
+ # record_number().
67
+ # Returns an empty Array ([]) if nothing is found.
68
+ #
69
+ def self.find_by_uid(uid)
70
+ base = "uid=#{uid},ou=people,dc=berkeley,dc=edu"
71
+ filter = Net::LDAP::Filter.eq("objectclass", 'berkeleyEduPersonJobAppt')
72
+ search(:base => base, :filter => filter).sort_by { |appt| appt.record_number }
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -1,50 +1,40 @@
1
-
2
- module UCB
3
- module LDAP
4
- ##
5
- # Class for accessing the Namespace/Name part of LDAP.
6
- #
7
- class Namespace < Entry
8
- @tree_base = 'ou=names,ou=namespace,dc=berkeley,dc=edu'
9
- @entity_name = 'namespaceName'
10
-
11
- ##
12
- # Returns name
13
- #
14
- def name
15
- cn.first
16
- end
17
-
18
- ##
19
- # Returns +Array+ of services
20
- #
21
- def services
22
- berkeleyEduServices
23
- end
24
-
25
- ##
26
- # Returns uid
27
- #
28
- def uid
29
- super.first
30
- end
31
-
32
- class << self
33
- ##
34
- # Returns an +Array+ of Namespace for _uid_.
35
- #
36
- def find_by_uid(uid)
37
- search(:filter => "uid=#{uid}")
38
- end
39
-
40
- ##
41
- # Returns Namespace instance for _cn_.
42
- #
43
- def find_by_cn(cn)
44
- search(:filter => "cn=#{cn}").first
45
- end
46
- end
47
- end
48
-
49
- end
50
- end
1
+ module UCB
2
+ module LDAP
3
+ ##
4
+ # Class for accessing the Namespace/Name part of LDAP.
5
+ #
6
+ class Namespace < Entry
7
+ @tree_base = 'ou=names,ou=namespace,dc=berkeley,dc=edu'
8
+ @entity_name = 'namespaceName'
9
+
10
+ def name
11
+ cn.first
12
+ end
13
+
14
+ ##
15
+ # Returns +Array+ of services
16
+ #
17
+ def services
18
+ berkeleyEduServices
19
+ end
20
+
21
+ def uid
22
+ super.first
23
+ end
24
+
25
+ ##
26
+ # Returns an +Array+ of Namespace for _uid_.
27
+ #
28
+ def self.find_by_uid(uid)
29
+ search(:filter => "uid=#{uid}")
30
+ end
31
+
32
+ ##
33
+ # Returns Namespace instance for _cn_.
34
+ #
35
+ def self.find_by_cn(cn)
36
+ search(:filter => "cn=#{cn}").first
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,429 +1,427 @@
1
-
2
- module UCB
3
- module LDAP
4
- ##
5
- # =UCB::LDAP::Org
6
- #
7
- # Class for accessing the Org Unit tree of the UCB LDAP directory.
8
- #
9
- # You can search by specifying your own filter:
10
- #
11
- # e = Org.search(:filter => 'ou=jkasd')
12
- #
13
- # But most of the time you'll use the find_by_ou() method:
14
- #
15
- # e = Org.find_by_ou('jkasd')
16
- #
17
- # Get attribute values as if the attribute names were instance methods.
18
- # Values returned reflect the cardinality and type as specified in the
19
- # LDAP schema.
20
- #
21
- # e = Org.find_by_ou('jkasd')
22
- #
23
- # e.ou #=> ['JKASD']
24
- # e.description #=> ['Application Services']
25
- # e.berkeleyEduOrgUnitProcessUnitFlag #=> true
26
- #
27
- # Convenience methods are provided that have friendlier names
28
- # and return scalars for attributes the schema says are mulit-valued,
29
- # but in practice are single-valued:
30
- #
31
- # e = Org.find_by_ou('jkasd')
32
- #
33
- # e.deptid #=> 'JKASD'
34
- # e.name #=> 'Application Services'
35
- # e.processing_unit? #=> true
36
- #
37
- # Other methods encapsulate common processing:
38
- #
39
- # e.level #=> 4
40
- # e.parent_node #=> #<UCB::LDAP::Org: ...>
41
- # e.parent_node.deptid #=> 'VRIST'
42
- # e.child_nodes #=> [#<UCB::LDAP::Org: ..>, ...]
43
- #
44
- #
45
- # You can retrieve people in a department. This will be
46
- # an +Array+ of UCB::LDAP::Person.
47
- #
48
- # asd = Org.find_by_ou('jkasd')
49
- #
50
- # asd_staff = asd.persons #=> [#<UCB::LDAP::Person: ...>, ...]
51
- #
52
- # === Getting a Node's Level "n" Code or Name
53
- #
54
- # There are methods that will return the org code and org name
55
- # at a particular level. They are implemented by method_missing
56
- # and so are not documented in the instance method section.
57
- #
58
- # o = Org.find_by_ou('jkasd')
59
- #
60
- # o.code #=> 'JKASD'
61
- # o.level #=> 4
62
- # o.level_4_code #=> 'JKASD'
63
- # o.level_3_name #=> 'Info Services & Technology'
64
- # o.level_2_code #=> 'AVCIS'
65
- # o.level_5_code #=> nil
66
- #
67
- # == Dealing With the Entire Org Tree
68
- #
69
- # There are several class methods that simplify most operations
70
- # involving the entire org tree.
71
- #
72
- # === Org.all_nodes()
73
- #
74
- # Returns a +Hash+ of all org nodes whose keys are deptids
75
- # and whose values are corresponding Org instances.
76
- #
77
- # # List all nodes alphabetically by department name
78
- #
79
- # nodes_by_name = Org.all_nodes.values.sort_by{|n| n.name.upcase}
80
- # nodes_by_name.each do |n|
81
- # puts "#{n.deptid} - #{n.name}"
82
- # end
83
- #
84
- # === Org.flattened_tree()
85
- #
86
- # Returns an +Array+ of all nodes in hierarchy order.
87
- #
88
- # UCB::LDAP::Org.flattened_tree.each do |node|
89
- # puts "#{node.level} #{node.deptid} - #{node.name}"
90
- # end
91
- #
92
- # Produces:
93
- #
94
- # 1 UCBKL - UC Berkeley Campus
95
- # 2 AVCIS - Information Sys & Technology
96
- # 3 VRIST - Info Systems & Technology
97
- # 4 JFAVC - Office of the CIO
98
- # 5 JFADM - Assoc VC Off General Ops
99
- # 4 JGMIP - Museum Informatics Project
100
- # 4 JHSSC - Social Sci Computing Lab
101
- # etc.
102
- #
103
- # === Org.root_node()
104
- #
105
- # Returns the root node in the Org Tree.
106
- #
107
- # By recursing down child_nodes you can access the entire
108
- # org tree:
109
- #
110
- # # display deptid, name and children recursively
111
- # def display_node(node)
112
- # indent = " " * (node.level - 1)
113
- # puts "#{indent} #{node.deptid} - #{node.name}"
114
- # node.child_nodes.each{|child| display_node(child)}
115
- # end
116
- #
117
- # # start at root node
118
- # display_node(Org.root_node)
119
- #
120
- # == Caching of Search Results
121
- #
122
- # Calls to any of the following class methods automatically cache
123
- # the entire Org tree:
124
- #
125
- # * all_nodes()
126
- # * flattened_tree()
127
- # * root_node()
128
- #
129
- # Subsequent calls to any of these methods return the results from
130
- # cache and don't require another LDAP query.
131
- #
132
- # Subsequent calls to find_by_ou() are done
133
- # against the local cache. Searches done via the #search()
134
- # method do <em>not</em> use the local cache.
135
- #
136
- # Force loading of the cache by calling load_all_nodes().
137
- #
138
- class Org < Entry
139
- @entity_name = "org"
140
- @tree_base = 'ou=org units,dc=berkeley,dc=edu'
141
-
142
- ##
143
- # Returns <tt>Array</tt> of child nodes, each an instance of Org,
144
- # sorted by department id.
145
- #
146
- def child_nodes()
147
- @sorted_child_nodes ||= load_child_nodes.sort_by { |node| node.deptid }
148
- end
149
-
150
- ##
151
- # Returns the department id.
152
- #
153
- def deptid()
154
- ou.first
155
- end
156
- alias :code :deptid
157
-
158
- ##
159
- # Returns the entry's level in the Org Tree.
160
- #
161
- def level()
162
- @level ||= parent_deptids.size + 1
163
- end
164
-
165
- ##
166
- # Returns the department name.
167
- #
168
- def name()
169
- description.first
170
- end
171
-
172
- ##
173
- # Returns parent node's deptid
174
- #
175
- def parent_deptid()
176
- @parent_deptid ||= parent_deptids.last
177
- end
178
-
179
- ##
180
- # Returns Array of parent deptids.
181
- #
182
- # Highest level is first element; immediate parent is last element.
183
- #
184
- def parent_deptids()
185
- return @parent_deptids if @parent_deptids
186
- hierarchy_array = berkeleyEduOrgUnitHierarchyString.split("-")
187
- hierarchy_array.pop # last element is deptid ... toss it
188
- @parent_deptids = hierarchy_array
189
- end
190
-
191
- ##
192
- # Returns +true+ if org is a processing unit.
193
- #
194
- def processing_unit?()
195
- berkeleyEduOrgUnitProcessUnitFlag
196
- end
197
-
198
- ##
199
- # Return parent node which is an instance of Org.
200
- #
201
- def parent_node()
202
- return nil if parent_deptids.size == 0
203
- @parent_node ||= UCB::LDAP::Org.find_by_ou parent_deptid
204
- end
205
-
206
- ##
207
- # Returns <tt>Array</tt> of parent nodes which are instances of Org.
208
- #
209
- def parent_nodes()
210
- @parent_nodes ||= parent_deptids.map { |deptid| UCB::LDAP::Org.find_by_ou(deptid) }
211
- end
212
-
213
- ##
214
- # Support for method names like level_2_code, level_2_name
215
- #
216
- def method_missing(method, *args) #:nodoc:
217
- return code_or_name_at_level($1, $2) if method.to_s =~ /^level_([1-6])_(code|name)$/
218
- super
219
- end
220
-
221
- ##
222
- # Return the level "n" code or name. Returns nil if level > self.level.
223
- # Called from method_messing().
224
- #
225
- def code_or_name_at_level(level, code_or_name) #:nodoc:
226
- return (code_or_name == 'code' ? code : name) if level.to_i == self.level
227
- element = level.to_i - 1
228
- return parent_deptids[element] if code_or_name == 'code'
229
- parent_nodes[element] && parent_nodes[element].name
230
- end
231
-
232
- ##
233
- # Returns <tt>Array</tt> of UCB::LDAP::Person instances for each person
234
- # in the org node.
235
- #
236
- def persons()
237
- @persons ||= UCB::LDAP::Person.search(:filter => {:departmentnumber => ou})
238
- end
239
- alias :people :persons
240
-
241
- #---
242
- # Must be public for load_all_nodes()
243
- def init_child_nodes() #:nodoc:
244
- @child_nodes = []
245
- end
246
-
247
- #---
248
- # Add node to child node array.
249
- def push_child_node(child_node)#:nodoc:
250
- @child_nodes ||= []
251
- unless @child_nodes.find { |n| n.ou == child_node.ou }
252
- @child_nodes.push(child_node)
253
- end
254
- end
255
-
256
- private unless $TESTING
257
-
258
- ##
259
- # Loads child nodes for individual node. If all_nodes_nodes()
260
- # has been called, child nodes are all loaded/calculated.
261
- #
262
- def load_child_nodes
263
- @child_nodes ||= UCB::LDAP::Org.search(:scope => 1, :base => dn, :filter => {:ou => '*'})
264
- end
265
-
266
- ##
267
- # Access to instance variables for testing
268
- #
269
- def child_nodes_i()
270
- @child_nodes
271
- end
272
-
273
-
274
- class << self
275
- public
276
-
277
- ##
278
- # Rebuild the org tree using fresh data from ldap
279
- #
280
- def rebuild_node_cache
281
- clear_all_nodes
282
- load_all_nodes
283
- end
284
-
285
- ##
286
- # Returns a +Hash+ of all org nodes whose keys are deptids
287
- # and whose values are corresponding Org instances.
288
- #
289
- def all_nodes()
290
- @all_nodes ||= load_all_nodes
291
- end
292
-
293
- ##
294
- # Returns an instance of Org for the matching _ou_.
295
- #
296
- def find_by_ou(ou)
297
- find_by_ou_from_cache(ou) || search(:filter => {:ou => ou}).first
298
- end
299
-
300
- ##
301
- # for backwards compatibility -- should be deprecated
302
- #
303
- alias :org_by_ou :find_by_ou
304
-
305
- ##
306
- # Returns an +Array+ of all nodes in hierarchy order. If you call
307
- # with <tt>:level</tt> option, only nodes down to that level are
308
- # returned.
309
- #
310
- # Org.flattened_tree # returns all nodes
311
- # Org.flattened_tree(:level => 3) # returns down to level 3
312
- #
313
- def flattened_tree(options={})
314
- @flattened_tree ||= build_flattened_tree
315
- return @flattened_tree unless options[:level]
316
- @flattened_tree.reject { |o| o.level > options[:level] }
317
- end
318
-
319
- ##
320
- # Loads all org nodes and stores them in Hash returned by all_nodes().
321
- # Subsequent calls to find_by_ou() will be from cache and not
322
- # require a trip to the LDAP server.
323
- #
324
- def load_all_nodes()
325
- return @all_nodes if @all_nodes
326
- return nodes_from_test_cache if $TESTING && @test_node_cache
327
-
328
- bind_for_whole_tree
329
- @all_nodes = search.inject({}) do |accum, org|
330
- accum[org.deptid] = org if org.deptid != "Org Units"
331
- accum
332
- end
333
-
334
- build_test_node_cache if $TESTING
335
- calculate_all_child_nodes
336
- UCB::LDAP.clear_authentication
337
- @all_nodes
338
- end
339
-
340
- ##
341
- # Returns the root node in the Org Tree.
342
- #
343
- def root_node()
344
- load_all_nodes
345
- find_by_ou('UCBKL')
346
- end
347
-
348
- private unless $TESTING
349
-
350
- ##
351
- # Use bind that allows for retreiving entire org tree in one search.
352
- #
353
- def bind_for_whole_tree()
354
- username = "uid=istaswa-ruby,ou=applications,dc=berkeley,dc=edu"
355
- password = "t00lBox12"
356
- UCB::LDAP.authenticate(username, password)
357
- end
358
-
359
- ##
360
- # Returns an instance of Org from the local if cache exists, else +nil+.
361
- #
362
- def find_by_ou_from_cache(ou) #:nodoc:
363
- return nil if ou.nil?
364
- return nil unless @all_nodes
365
- @all_nodes[ou.upcase]
366
- end
367
-
368
- ##
369
- # Returns cached nodes if we are testing and have already
370
- # fetched all the nodes.
371
- #
372
- def nodes_from_test_cache()
373
- @all_nodes = {}
374
- @test_node_cache.each { |k, v| @all_nodes[k] = v.clone }
375
- calculate_all_child_nodes
376
- @all_nodes
377
- end
378
-
379
- ##
380
- # Build cache of all nodes. Only used during testing.
381
- #
382
- def build_test_node_cache()
383
- @test_node_cache = {}
384
- @all_nodes.each { |k, v| @test_node_cache[k] = v.clone }
385
- end
386
-
387
- ##
388
- # Will calculate child_nodes for every node.
389
- #
390
- def calculate_all_child_nodes()
391
- @all_nodes.values.each { |node| node.init_child_nodes }
392
- @all_nodes.values.each do |node|
393
- next if node.deptid == 'UCBKL' || node.deptid == "Org Units"
394
- parent_node = find_by_ou_from_cache(node.parent_deptids.last)
395
- parent_node.push_child_node(node)
396
- end
397
- end
398
-
399
- ##
400
- # Builds flattened tree. See RDoc for flattened_tree() for details.
401
- #
402
- def build_flattened_tree()
403
- load_all_nodes
404
- @flattened_tree = []
405
- add_to_flattened_tree UCB::LDAP::Org.root_node
406
- @flattened_tree
407
- end
408
-
409
- ##
410
- # Adds a node and its children to @flattened_tree.
411
- #
412
- def add_to_flattened_tree(node)
413
- @flattened_tree.push node
414
- node.child_nodes.each { |child| add_to_flattened_tree child }
415
- end
416
-
417
- # Direct access to instance variables for unit testing
418
-
419
- def all_nodes_i()
420
- @all_nodes
421
- end
422
-
423
- def clear_all_nodes()
424
- @all_nodes = nil
425
- end
426
- end
427
- end
428
- end
429
- end
1
+ module UCB
2
+ module LDAP
3
+ ##
4
+ # =UCB::LDAP::Org
5
+ #
6
+ # Class for accessing the Org Unit tree of the UCB LDAP directory.
7
+ #
8
+ # You can search by specifying your own filter:
9
+ #
10
+ # e = Org.search(:filter => 'ou=jkasd')
11
+ #
12
+ # But most of the time you'll use the find_by_ou() method:
13
+ #
14
+ # e = Org.find_by_ou('jkasd')
15
+ #
16
+ # Get attribute values as if the attribute names were instance methods.
17
+ # Values returned reflect the cardinality and type as specified in the
18
+ # LDAP schema.
19
+ #
20
+ # e = Org.find_by_ou('jkasd')
21
+ #
22
+ # e.ou #=> ['JKASD']
23
+ # e.description #=> ['Application Services']
24
+ # e.berkeleyEduOrgUnitProcessUnitFlag #=> true
25
+ #
26
+ # Convenience methods are provided that have friendlier names
27
+ # and return scalars for attributes the schema says are mulit-valued,
28
+ # but in practice are single-valued:
29
+ #
30
+ # e = Org.find_by_ou('jkasd')
31
+ #
32
+ # e.deptid #=> 'JKASD'
33
+ # e.name #=> 'Application Services'
34
+ # e.processing_unit? #=> true
35
+ #
36
+ # Other methods encapsulate common processing:
37
+ #
38
+ # e.level #=> 4
39
+ # e.parent_node #=> #<UCB::LDAP::Org: ...>
40
+ # e.parent_node.deptid #=> 'VRIST'
41
+ # e.child_nodes #=> [#<UCB::LDAP::Org: ..>, ...]
42
+ #
43
+ #
44
+ # You can retrieve people in a department. This will be
45
+ # an +Array+ of UCB::LDAP::Person.
46
+ #
47
+ # asd = Org.find_by_ou('jkasd')
48
+ #
49
+ # asd_staff = asd.persons #=> [#<UCB::LDAP::Person: ...>, ...]
50
+ #
51
+ # === Getting a Node's Level "n" Code or Name
52
+ #
53
+ # There are methods that will return the org code and org name
54
+ # at a particular level. They are implemented by method_missing
55
+ # and so are not documented in the instance method section.
56
+ #
57
+ # o = Org.find_by_ou('jkasd')
58
+ #
59
+ # o.code #=> 'JKASD'
60
+ # o.level #=> 4
61
+ # o.level_4_code #=> 'JKASD'
62
+ # o.level_3_name #=> 'Info Services & Technology'
63
+ # o.level_2_code #=> 'AVCIS'
64
+ # o.level_5_code #=> nil
65
+ #
66
+ # == Dealing With the Entire Org Tree
67
+ #
68
+ # There are several class methods that simplify most operations
69
+ # involving the entire org tree.
70
+ #
71
+ # === Org.all_nodes()
72
+ #
73
+ # Returns a +Hash+ of all org nodes whose keys are deptids
74
+ # and whose values are corresponding Org instances.
75
+ #
76
+ # # List all nodes alphabetically by department name
77
+ #
78
+ # nodes_by_name = Org.all_nodes.values.sort_by{|n| n.name.upcase}
79
+ # nodes_by_name.each do |n|
80
+ # puts "#{n.deptid} - #{n.name}"
81
+ # end
82
+ #
83
+ # === Org.flattened_tree()
84
+ #
85
+ # Returns an +Array+ of all nodes in hierarchy order.
86
+ #
87
+ # UCB::LDAP::Org.flattened_tree.each do |node|
88
+ # puts "#{node.level} #{node.deptid} - #{node.name}"
89
+ # end
90
+ #
91
+ # Produces:
92
+ #
93
+ # 1 UCBKL - UC Berkeley Campus
94
+ # 2 AVCIS - Information Sys & Technology
95
+ # 3 VRIST - Info Systems & Technology
96
+ # 4 JFAVC - Office of the CIO
97
+ # 5 JFADM - Assoc VC Off General Ops
98
+ # 4 JGMIP - Museum Informatics Project
99
+ # 4 JHSSC - Social Sci Computing Lab
100
+ # etc.
101
+ #
102
+ # === Org.root_node()
103
+ #
104
+ # Returns the root node in the Org Tree.
105
+ #
106
+ # By recursing down child_nodes you can access the entire
107
+ # org tree:
108
+ #
109
+ # # display deptid, name and children recursively
110
+ # def display_node(node)
111
+ # indent = " " * (node.level - 1)
112
+ # puts "#{indent} #{node.deptid} - #{node.name}"
113
+ # node.child_nodes.each{|child| display_node(child)}
114
+ # end
115
+ #
116
+ # # start at root node
117
+ # display_node(Org.root_node)
118
+ #
119
+ # == Caching of Search Results
120
+ #
121
+ # Calls to any of the following class methods automatically cache
122
+ # the entire Org tree:
123
+ #
124
+ # * all_nodes()
125
+ # * flattened_tree()
126
+ # * root_node()
127
+ #
128
+ # Subsequent calls to any of these methods return the results from
129
+ # cache and don't require another LDAP query.
130
+ #
131
+ # Subsequent calls to find_by_ou() are done
132
+ # against the local cache. Searches done via the #search()
133
+ # method do <em>not</em> use the local cache.
134
+ #
135
+ # Force loading of the cache by calling load_all_nodes().
136
+ #
137
+ class Org < Entry
138
+ @entity_name = "org"
139
+ @tree_base = 'ou=org units,dc=berkeley,dc=edu'
140
+
141
+ ##
142
+ # Returns <tt>Array</tt> of child nodes, each an instance of Org,
143
+ # sorted by department id.
144
+ #
145
+ def child_nodes
146
+ @sorted_child_nodes ||= load_child_nodes.sort_by { |node| node.deptid }
147
+ end
148
+
149
+ ##
150
+ # Returns the department id.
151
+ #
152
+ def deptid
153
+ ou.first
154
+ end
155
+
156
+ alias :code :deptid
157
+
158
+ ##
159
+ # Returns the entry's level in the Org Tree.
160
+ #
161
+ def level
162
+ @level ||= parent_deptids.size + 1
163
+ end
164
+
165
+ ##
166
+ # Returns the department name.
167
+ #
168
+ def name
169
+ description.first
170
+ end
171
+
172
+ ##
173
+ # Returns parent node's deptid
174
+ #
175
+ def parent_deptid
176
+ @parent_deptid ||= parent_deptids.last
177
+ end
178
+
179
+ ##
180
+ # Returns Array of parent deptids.
181
+ #
182
+ # Highest level is first element; immediate parent is last element.
183
+ #
184
+ def parent_deptids
185
+ return @parent_deptids if @parent_deptids
186
+ hierarchy_array = berkeleyEduOrgUnitHierarchyString.split("-")
187
+ hierarchy_array.pop # last element is deptid ... toss it
188
+ @parent_deptids = hierarchy_array
189
+ end
190
+
191
+ ##
192
+ # Returns +true+ if org is a processing unit.
193
+ #
194
+ def processing_unit?
195
+ berkeleyEduOrgUnitProcessUnitFlag
196
+ end
197
+
198
+ ##
199
+ # Return parent node which is an instance of Org.
200
+ #
201
+ def parent_node
202
+ return nil if parent_deptids.size == 0
203
+ @parent_node ||= UCB::LDAP::Org.find_by_ou(parent_deptid)
204
+ end
205
+
206
+ ##
207
+ # Returns <tt>Array</tt> of parent nodes which are instances of Org.
208
+ #
209
+ def parent_nodes
210
+ @parent_nodes ||= parent_deptids.map { |deptid| UCB::LDAP::Org.find_by_ou(deptid) }
211
+ end
212
+
213
+ ##
214
+ # Support for method names like level_2_code, level_2_name
215
+ #
216
+ def method_missing(method, *args) #:nodoc:
217
+ return code_or_name_at_level($1, $2) if method.to_s =~ /^level_([1-6])_(code|name)$/
218
+ super
219
+ end
220
+
221
+ ##
222
+ # Return the level "n" code or name. Returns nil if level > self.level.
223
+ # Called from method_messing().
224
+ #
225
+ def code_or_name_at_level(level, code_or_name) #:nodoc:
226
+ return (code_or_name == 'code' ? code : name) if level.to_i == self.level
227
+ element = level.to_i - 1
228
+ return parent_deptids[element] if code_or_name == 'code'
229
+ parent_nodes[element] && parent_nodes[element].name
230
+ end
231
+
232
+ ##
233
+ # Returns <tt>Array</tt> of UCB::LDAP::Person instances for each person
234
+ # in the org node.
235
+ #
236
+ def persons
237
+ @persons ||= UCB::LDAP::Person.search(:filter => { :departmentnumber => ou.first.to_s })
238
+ end
239
+
240
+ alias :people :persons
241
+
242
+ #---
243
+ # Must be public for load_all_nodes()
244
+ def init_child_nodes #:nodoc:
245
+ @child_nodes = []
246
+ end
247
+
248
+ #---
249
+ # Add node to child node array.
250
+ def push_child_node(child_node) #:nodoc:
251
+ @child_nodes ||= []
252
+ unless @child_nodes.find { |n| n.ou == child_node.ou }
253
+ @child_nodes.push(child_node)
254
+ end
255
+ end
256
+
257
+ ## TODO: restore private
258
+ #private unless $TESTING
259
+
260
+ ##
261
+ # Loads child nodes for individual node. If all_nodes_nodes()
262
+ # has been called, child nodes are all loaded/calculated.
263
+ #
264
+ def load_child_nodes
265
+ @child_nodes ||= UCB::LDAP::Org.search(:scope => 1, :base => dn, :filter => { :ou => '*' })
266
+ end
267
+
268
+ ##
269
+ # Access to instance variables for testing
270
+ #
271
+ def child_nodes_i
272
+ @child_nodes
273
+ end
274
+
275
+
276
+ class << self
277
+ #public
278
+
279
+ ##
280
+ # Rebuild the org tree using fresh data from ldap
281
+ #
282
+ def rebuild_node_cache
283
+ clear_all_nodes
284
+ load_all_nodes
285
+ end
286
+
287
+ ##
288
+ # Returns a +Hash+ of all org nodes whose keys are deptids
289
+ # and whose values are corresponding Org instances.
290
+ #
291
+ def all_nodes
292
+ @all_nodes ||= load_all_nodes
293
+ end
294
+
295
+ ##
296
+ # Returns an instance of Org for the matching _ou_.
297
+ #
298
+ def find_by_ou(ou)
299
+ find_by_ou_from_cache(ou) || search(:filter => { :ou => ou }).first
300
+ end
301
+
302
+ ##
303
+ # for backwards compatibility -- should be deprecated
304
+ #
305
+ alias :org_by_ou :find_by_ou
306
+
307
+ ##
308
+ # Returns an +Array+ of all nodes in hierarchy order. If you call
309
+ # with <tt>:level</tt> option, only nodes down to that level are
310
+ # returned.
311
+ #
312
+ # Org.flattened_tree # returns all nodes
313
+ # Org.flattened_tree(:level => 3) # returns down to level 3
314
+ #
315
+ def flattened_tree(options={})
316
+ @flattened_tree ||= build_flattened_tree
317
+ return @flattened_tree unless options[:level]
318
+ @flattened_tree.reject { |o| o.level > options[:level] }
319
+ end
320
+
321
+ ##
322
+ # Loads all org nodes and stores them in Hash returned by all_nodes().
323
+ # Subsequent calls to find_by_ou() will be from cache and not
324
+ # require a trip to the LDAP server.
325
+ #
326
+ def load_all_nodes
327
+ return @all_nodes if @all_nodes
328
+ return nodes_from_test_cache if $TESTING && @test_node_cache
329
+
330
+ tree_username = "uid=istaswa-ruby,ou=applications,dc=berkeley,dc=edu"
331
+ tree_password = "t00lBox12"
332
+
333
+ UCB::LDAP.with_credentials(tree_username, tree_password) do
334
+ @all_nodes = search.inject({}) do |accum, org|
335
+ accum[org.deptid] = org if org.deptid != "Org Units"
336
+ accum
337
+ end
338
+ end
339
+
340
+ build_test_node_cache if $TESTING
341
+ calculate_all_child_nodes
342
+ @all_nodes
343
+ end
344
+
345
+ ##
346
+ # Returns the root node in the Org Tree.
347
+ #
348
+ def root_node
349
+ load_all_nodes
350
+ find_by_ou('UCBKL')
351
+ end
352
+
353
+ ## TODO restore private
354
+ #private unless $TESTING
355
+
356
+ ##
357
+ # Returns an instance of Org from the local if cache exists, else +nil+.
358
+ #
359
+ def find_by_ou_from_cache(ou) #:nodoc:
360
+ return nil if ou.nil?
361
+ return nil unless @all_nodes
362
+ @all_nodes[ou.upcase]
363
+ end
364
+
365
+ ##
366
+ # Returns cached nodes if we are testing and have already
367
+ # fetched all the nodes.
368
+ #
369
+ def nodes_from_test_cache
370
+ @all_nodes = {}
371
+ @test_node_cache.each { |k, v| @all_nodes[k] = v.clone }
372
+ calculate_all_child_nodes
373
+ @all_nodes
374
+ end
375
+
376
+ ##
377
+ # Build cache of all nodes. Only used during testing.
378
+ #
379
+ def build_test_node_cache
380
+ @test_node_cache = {}
381
+ @all_nodes.each { |k, v| @test_node_cache[k] = v.clone }
382
+ end
383
+
384
+ ##
385
+ # Will calculate child_nodes for every node.
386
+ #
387
+ def calculate_all_child_nodes
388
+ @all_nodes.values.each { |node| node.init_child_nodes }
389
+ @all_nodes.values.each do |node|
390
+ next if node.deptid == 'UCBKL' || node.deptid == "Org Units"
391
+ parent_node = find_by_ou_from_cache(node.parent_deptids.last)
392
+ parent_node.push_child_node(node)
393
+ end
394
+ end
395
+
396
+ ##
397
+ # Builds flattened tree. See RDoc for flattened_tree() for details.
398
+ #
399
+ def build_flattened_tree
400
+ load_all_nodes
401
+ @flattened_tree = []
402
+ add_to_flattened_tree(UCB::LDAP::Org.root_node)
403
+ @flattened_tree
404
+ end
405
+
406
+ ##
407
+ # Adds a node and its children to @flattened_tree.
408
+ #
409
+ def add_to_flattened_tree(node)
410
+ @flattened_tree.push(node)
411
+ node.child_nodes.each { |child| add_to_flattened_tree(child) }
412
+ end
413
+
414
+ # Direct access to instance variables for unit testing
415
+
416
+ def all_nodes_i
417
+ @all_nodes
418
+ end
419
+
420
+ def clear_all_nodes
421
+ @all_nodes = nil
422
+ end
423
+ end
424
+ end
425
+ end
426
+ end
427
+