ucb_ldap 1.3.0

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.
@@ -0,0 +1,27 @@
1
+ module UCB
2
+ module LDAP
3
+
4
+ class BadAttributeNameException < Exception #:nodoc:
5
+ end
6
+
7
+ class BindFailedException < Exception #:nodoc:
8
+ def initialize
9
+ super("Failed to bind username '#{UCB::LDAP.username}' to '#{UCB::LDAP.host}'")
10
+ end
11
+ end
12
+
13
+ class ConnectionFailedException < Exception #:nodoc:
14
+ def initialize
15
+ super("Failed to connect to ldap host '#{UCB::LDAP.host}''")
16
+ end
17
+ end
18
+
19
+ class DirectoryNotUpdatedException < Exception #:nodoc:
20
+ def initialize
21
+ result = UCB::LDAP.net_ldap.get_operation_result
22
+ super("(Code=#{result.code}) #{result.message}")
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,42 @@
1
+
2
+ module UCB
3
+ module LDAP
4
+ # Class for accessing the Namespace/Name part of LDAP.
5
+ class Namespace < Entry
6
+
7
+ @tree_base = 'ou=names,ou=namespace,dc=berkeley,dc=edu'
8
+ @entity_name = 'namespaceName'
9
+
10
+ # Returns name
11
+ def name
12
+ cn.first
13
+ end
14
+
15
+ # Returns +Array+ of services
16
+ def services
17
+ berkeleyEduServices
18
+ end
19
+
20
+ # Returns uid
21
+ def uid
22
+ super.first
23
+ end
24
+
25
+ # class methods
26
+ class << self
27
+
28
+ # Returns an +Array+ of Namespace for _uid_.
29
+ def find_by_uid(uid)
30
+ search(:filter => "uid=#{uid}")
31
+ end
32
+
33
+ # Returns Namespace instance for _cn_.
34
+ def find_by_cn(cn)
35
+ search(:filter => "cn=#{cn}").first
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,369 @@
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
+ # Returns <tt>Array</tt> of child nodes, each an instance of Org,
143
+ # sorted by department id.
144
+ def child_nodes
145
+ @sorted_child_nodes ||= load_child_nodes.sort_by{|node| node.deptid}
146
+ end
147
+
148
+ # Returns the department id.
149
+ def deptid
150
+ ou.first
151
+ end
152
+ alias :code :deptid
153
+
154
+ # Returns the entry's level in the Org Tree.
155
+ def level
156
+ @level ||= parent_deptids.size + 1
157
+ end
158
+
159
+ # Returns the department name.
160
+ def name
161
+ description.first
162
+ end
163
+
164
+ # Returns parent node's deptid
165
+ def parent_deptid
166
+ @parent_deptid ||= parent_deptids.last
167
+ end
168
+
169
+ # Returns Array of parent deptids.
170
+ #
171
+ # Highest level is first element; immediate parent is last element.
172
+ def parent_deptids
173
+ return @parent_deptids if @parent_deptids
174
+ hierarchy_array = berkeleyEduOrgUnitHierarchyString.split("-")
175
+ hierarchy_array.pop # last element is deptid ... toss it
176
+ @parent_deptids = hierarchy_array
177
+ end
178
+
179
+ # Returns +true+ if org is a processing unit.
180
+ def processing_unit?
181
+ berkeleyEduOrgUnitProcessUnitFlag
182
+ end
183
+
184
+ # Return parent node which is an instance of Org.
185
+ def parent_node
186
+ return nil if parent_deptids.size == 0
187
+ @parent_node ||= UCB::LDAP::Org.find_by_ou parent_deptid
188
+ end
189
+
190
+ # Returns <tt>Array</tt> of parent nodes which are instances of Org.
191
+ def parent_nodes
192
+ @parent_nodes ||= parent_deptids.map{|deptid| UCB::LDAP::Org.find_by_ou deptid}
193
+ end
194
+
195
+ # Support for method names like level_2_code, level_2_name
196
+ def method_missing(method, *args) #:nodoc:
197
+ return code_or_name_at_level($1, $2) if method.to_s =~ /^level_([1-6])_(code|name)$/
198
+ super
199
+ end
200
+
201
+ # Return the level "n" code or name. Returns nil if level > self.level.
202
+ # Called from method_messing().
203
+ def code_or_name_at_level(level, code_or_name) #:nodoc:
204
+ return (code_or_name == 'code' ? code : name) if level.to_i == self.level
205
+ element = level.to_i - 1
206
+ return parent_deptids[element] if code_or_name == 'code'
207
+ parent_nodes[element] && parent_nodes[element].name
208
+ end
209
+
210
+ # Returns <tt>Array</tt> of UCB::LDAP::Person instances for each person
211
+ # in the org node.
212
+ def persons
213
+ @persons ||= UCB::LDAP::Person.search(:filter => {:departmentnumber => ou})
214
+ end
215
+
216
+ #---
217
+ # Must be public for load_all_nodes()
218
+ def init_child_nodes #:nodoc:
219
+ @child_nodes = []
220
+ end
221
+
222
+ #---
223
+ # Add node to child node array.
224
+ def push_child_node(child_node)#:nodoc:
225
+ @child_nodes ||= []
226
+ @child_nodes.push(child_node) unless # it already exists
227
+ @child_nodes.find{|n| n.ou == child_node.ou}
228
+ end
229
+
230
+ private unless $TESTING
231
+
232
+ # Loads child nodes for individual node. If all_nodes_nodes()
233
+ # has been called, child nodes are all loaded/calculated.
234
+ def load_child_nodes
235
+ @child_nodes ||= UCB::LDAP::Org.search(:scope => 1, :base => dn, :filter => {:ou => '*'})
236
+ end
237
+
238
+ # Access to instance variables for testing
239
+
240
+ def child_nodes_i
241
+ @child_nodes
242
+ end
243
+
244
+ # Class methods
245
+ class << self
246
+ public
247
+
248
+ # Returns a +Hash+ of all org nodes whose keys are deptids
249
+ # and whose values are corresponding Org instances.
250
+ def all_nodes
251
+ @all_nodes ||= load_all_nodes
252
+ end
253
+
254
+ # Returns an instance of Org for the matching _ou_.
255
+ def find_by_ou(ou)
256
+ find_by_ou_from_cache(ou) || search(:filter => {:ou => ou}).first
257
+ end
258
+
259
+ # for backwards compatibility -- should be deprecated
260
+ alias :org_by_ou :find_by_ou
261
+
262
+ # Returns an +Array+ of all nodes in hierarchy order. If you call
263
+ # with <tt>:level</tt> option, only nodes down to that level are
264
+ # returned.
265
+ #
266
+ # Org.flattened_tree # returns all nodes
267
+ # Org.flattened_tree(:level => 3) # returns down to level 3
268
+ def flattened_tree(options={})
269
+ @flattened_tree ||= build_flattened_tree
270
+ return @flattened_tree unless options[:level]
271
+ @flattened_tree.reject{|o| o.level > options[:level]}
272
+ end
273
+
274
+ # Loads all org nodes and stores then in Hash returned by all_nodes().
275
+ # Subsequent calls to find_by_ou() will be from cache and not
276
+ # require a trip to the LDAP server.
277
+ def load_all_nodes
278
+ return @all_nodes if @all_nodes
279
+ return nodes_from_test_cache if $TESTING && @test_node_cache
280
+
281
+ @all_nodes = {}
282
+ bind_for_whole_tree
283
+ search(:filter => 'ou=*', :base => @tree_base).each do |org|
284
+ @all_nodes[org.deptid] = org unless org.deptid == "Org Units"
285
+ end
286
+
287
+ build_test_node_cache if $TESTING
288
+ calculate_all_child_nodes
289
+ UCB::LDAP.clear_authentication
290
+ @all_nodes
291
+ end
292
+
293
+ # Returns the root node in the Org Tree.
294
+ def root_node
295
+ load_all_nodes
296
+ find_by_ou 'UCBKL'
297
+ end
298
+
299
+ private unless $TESTING
300
+
301
+ # Use bind that allows for retreiving entire org tree in one search.
302
+ def bind_for_whole_tree
303
+ username = "uid=istaswa-ruby,ou=applications,dc=berkeley,dc=edu"
304
+ password = "t00lBox12"
305
+ UCB::LDAP.authenticate(username, password)
306
+ end
307
+
308
+ # Returns an instance of Org from the local if cache exists, else +nil+.
309
+ def find_by_ou_from_cache(ou) #:nodoc:
310
+ return nil if ou.nil?
311
+ return nil unless @all_nodes
312
+ @all_nodes[ou.upcase]
313
+ end
314
+
315
+ # Returns cached nodes if we are testing and have already
316
+ # fetched all the nodes.
317
+ def nodes_from_test_cache
318
+ @all_nodes = {}
319
+ @test_node_cache.each{|k, v| @all_nodes[k] = v.clone}
320
+ calculate_all_child_nodes
321
+ @all_nodes
322
+ end
323
+
324
+ # Build cache of all nodes. Only used during testing.
325
+ def build_test_node_cache()
326
+ @test_node_cache = {}
327
+ @all_nodes.each{|k, v| @test_node_cache[k] = v.clone}
328
+ end
329
+
330
+ # Will calculate child_nodes for every node.
331
+ def calculate_all_child_nodes
332
+ @all_nodes.values.each{|node| node.init_child_nodes}
333
+
334
+ @all_nodes.values.each do |node|
335
+ next if node.deptid == 'UCBKL' || node.deptid == "Org Units"
336
+ parent_node = find_by_ou_from_cache node.parent_deptids.last
337
+ parent_node.push_child_node node
338
+ end
339
+ end
340
+
341
+ # Builds flattened tree. See RDoc for flattened_tree() for details.
342
+ def build_flattened_tree
343
+ load_all_nodes
344
+ @flattened_tree = []
345
+ add_to_flattened_tree UCB::LDAP::Org.root_node
346
+ @flattened_tree
347
+ end
348
+
349
+ # Adds a node and its children to @flattened_tree.
350
+ def add_to_flattened_tree(node)
351
+ @flattened_tree.push node
352
+ node.child_nodes.each{|child| add_to_flattened_tree child}
353
+ end
354
+
355
+ # Direct access to instance variables for unit testing
356
+
357
+ def all_nodes_i
358
+ @all_nodes
359
+ end
360
+
361
+ def clear_all_nodes
362
+ @all_nodes = nil
363
+ end
364
+
365
+ end # end of class methods
366
+
367
+ end
368
+ end
369
+ end
@@ -0,0 +1,135 @@
1
+
2
+ module UCB::LDAP
3
+ # =UCB::LDAP::Person
4
+ #
5
+ # Class for accessing the People tree of the UCB LDAP directory.
6
+ #
7
+ # You can search by specifying your own filter:
8
+ #
9
+ # e = Person.search(:filter => {:uid => 123})
10
+ #
11
+ # Or you can use a convenience search method:
12
+ #
13
+ # e = Person.find_by_uid("123")
14
+ #
15
+ # Access attributes as if they were instance methods:
16
+ #
17
+ # e = Person.find_by_uid("123")
18
+ #
19
+ # e.givenname #=> "John"
20
+ # e.sn #=> "Doe"
21
+ #
22
+ # Methods with friendly names are provided for accessing attribute values:
23
+ #
24
+ # e = Person.person_by_uid("123")
25
+ #
26
+ # e.firstname #=> "John"
27
+ # e.lastname #=> "Doe"
28
+ #
29
+ # There are other convenience methods:
30
+ #
31
+ # e = Person.person_by_uid("123")
32
+ #
33
+ # e.affiliations #=> ["EMPLOYEE-TYPE-STAFF"]
34
+ # e.employee? #=> true
35
+ # e.employee_staff? #=> true
36
+ # e.employee_academic? #=> false
37
+ # e.student? #=> false
38
+ #
39
+ # == Other Parts of the Tree
40
+ #
41
+ # You can access other parts of the LDAP directory through Person
42
+ # instances:
43
+ #
44
+ # p = Person.find_by_uid("123")
45
+ #
46
+ # p.org_node #=> Org
47
+ # p.affiliations #=> Array of Affiliation
48
+ # p.addresses #=> Array of Address
49
+ # p.job_appointments #=> Array of JobAppointment
50
+ # p.namespaces #=> Array of Namespace
51
+ # p.student_terms #=> Array of StudentTerm
52
+ #
53
+ # ==Attributes
54
+ #
55
+ # See Ldap::Entry for general information on accessing attribute values.
56
+ #
57
+ class Person < Entry
58
+ class RecordNotFound < Exception
59
+ end
60
+
61
+ include AffiliationMethods
62
+ include GenericAttributes
63
+
64
+ @entity_name = 'person'
65
+ @tree_base = 'ou=people,dc=berkeley,dc=edu'
66
+
67
+
68
+ class << self
69
+
70
+ # Returns an instance of Person for given _uid_.
71
+ def find_by_uid(uid)
72
+ uid = uid.to_s
73
+ find_by_uids([uid]).first
74
+ end
75
+ alias :person_by_uid :find_by_uid
76
+
77
+ # Returns an +Array+ of Person for given _uids_.
78
+ def find_by_uids(uids)
79
+ return [] if uids.size == 0
80
+ filters = uids.map{|uid| Net::LDAP::Filter.eq("uid", uid)}
81
+ search(:filter => self.combine_filters(filters, '|'))
82
+ end
83
+ alias :persons_by_uids :find_by_uids
84
+
85
+ # Exclude test entries from search results unless told otherwise.
86
+ def search(args) #:nodoc:
87
+ results = super
88
+ include_test_entries? ? results : results.reject{|person| person.test?}
89
+ end
90
+
91
+ # If <tt>true</tt> test entries are included in search results
92
+ # (defalut is <tt>false</tt>).
93
+ def include_test_entries?
94
+ @include_test_entries ? true : false
95
+ end
96
+
97
+ # Setter for include_test_entries?
98
+ def include_test_entries=(include_test_entries)
99
+ @include_test_entries = include_test_entries
100
+ end
101
+
102
+ end # end of class methods
103
+
104
+
105
+ def deptid
106
+ berkeleyEduPrimaryDeptUnit
107
+ end
108
+ alias :dept_code :deptid
109
+
110
+ def dept_name
111
+ berkeleyEduUnitCalNetDeptName
112
+ end
113
+
114
+ # Returns +Array+ of JobAppointment for this Person.
115
+ # Requires a bind with access to job appointments.
116
+ # See UCB::LDAP.authenticate().
117
+ def job_appointments
118
+ @job_appointments ||= JobAppointment.find_by_uid(uid)
119
+ end
120
+
121
+ # Returns +Array+ of StudentTerm for this Person.
122
+ # Requires a bind with access to student terms.
123
+ # See UCB::LDAP.authenticate().
124
+ def student_terms
125
+ @student_terms ||= StudentTerm.find_by_uid(uid)
126
+ end
127
+
128
+ # Returns instance of UCB::LDAP::Org corresponding to
129
+ # primary department.
130
+ def org_node
131
+ @org_node ||= UCB::LDAP::Org.find_by_ou(deptid)
132
+ end
133
+
134
+ end
135
+ end