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.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/CHANGELOG +137 -135
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/{README → README.md} +82 -80
- data/Rakefile +38 -20
- data/lib/ucb_ldap.rb +238 -204
- data/lib/{ucb_ldap_address.rb → ucb_ldap/address.rb} +106 -106
- data/lib/{ucb_ldap_affiliation.rb → ucb_ldap/affiliation.rb} +16 -16
- data/lib/{ucb_ldap_entry.rb → ucb_ldap/entry.rb} +455 -448
- data/lib/{ucb_ldap_person_job_appointment.rb → ucb_ldap/job_appointment.rb} +77 -79
- data/lib/{ucb_ldap_namespace.rb → ucb_ldap/namespace.rb} +40 -50
- data/lib/{ucb_ldap_org.rb → ucb_ldap/org.rb} +427 -429
- data/lib/{ucb_ldap_person.rb → ucb_ldap/person.rb} +157 -148
- data/lib/{person → ucb_ldap/person}/affiliation_methods.rb +23 -22
- data/lib/ucb_ldap/person/common_attributes.rb +63 -0
- data/lib/{ucb_ldap_schema.rb → ucb_ldap/schema.rb} +28 -28
- data/lib/{ucb_ldap_schema_attribute.rb → ucb_ldap/schema_attribute.rb} +152 -153
- data/lib/{ucb_ldap_service.rb → ucb_ldap/service.rb} +17 -19
- data/lib/{ucb_ldap_student_term.rb → ucb_ldap/student_term.rb} +29 -31
- data/lib/ucb_ldap/version.rb +3 -0
- data/spec/rails_binds.yml +9 -0
- data/spec/spec_helper.rb +43 -0
- data/spec/ucb_ldap/address_spec.rb +54 -0
- data/spec/ucb_ldap/affiliation_spec.rb +85 -0
- data/spec/ucb_ldap/entry_spec.rb +241 -0
- data/spec/ucb_ldap/job_appointment_spec.rb +65 -0
- data/spec/ucb_ldap/namespace_spec.rb +72 -0
- data/spec/ucb_ldap/org_spec.rb +217 -0
- data/spec/ucb_ldap/person_spec.rb +225 -0
- data/spec/ucb_ldap/schema_attribute_spec.rb +122 -0
- data/spec/ucb_ldap/schema_spec.rb +104 -0
- data/spec/ucb_ldap/service_spec.rb +127 -0
- data/spec/ucb_ldap/student_term_spec.rb +121 -0
- data/spec/ucb_ldap_spec.rb +182 -0
- data/ucb_ldap.gemspec +20 -27
- metadata +113 -64
- data/Manifest +0 -23
- data/TODO +0 -2
- data/lib/person/adv_con_person.rb +0 -0
- data/lib/person/generic_attributes.rb +0 -68
- data/lib/ucb_ldap_exceptions.rb +0 -27
- data/version.yml +0 -1
@@ -1,106 +1,106 @@
|
|
1
|
-
|
2
|
-
module UCB
|
3
|
-
module LDAP
|
4
|
-
# = UCB::LDAP::Address
|
5
|
-
#
|
6
|
-
# This class models a person address instance in the UCB LDAP directory.
|
7
|
-
#
|
8
|
-
# a = Address.find_by_uid("1234") #=> [#<UCB::LDAP::Address: ...>, ...]
|
9
|
-
#
|
10
|
-
# Addresses are usually loaded through a Person instance:
|
11
|
-
#
|
12
|
-
# p = Person.find_by_uid("1234") #=> #<UCB::LDAP::Person: ...>
|
13
|
-
# addrs = p.addresses #=> [#<UCB::LDAP::Address: ...>, ...]
|
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 Address search.
|
19
|
-
#
|
20
|
-
class Address < Entry
|
21
|
-
@entity_name = 'personAddress'
|
22
|
-
|
23
|
-
def primary_work_address?
|
24
|
-
berkeleyEduPersonAddressPrimaryFlag
|
25
|
-
end
|
26
|
-
|
27
|
-
def address_type
|
28
|
-
berkeleyEduPersonAddressType
|
29
|
-
end
|
30
|
-
|
31
|
-
def building_code
|
32
|
-
berkeleyEduPersonAddressBuildingCode
|
33
|
-
end
|
34
|
-
|
35
|
-
def city
|
36
|
-
l.first
|
37
|
-
end
|
38
|
-
|
39
|
-
def country_code
|
40
|
-
berkeleyEduPersonAddressCountryCode
|
41
|
-
end
|
42
|
-
|
43
|
-
def department_name
|
44
|
-
berkeleyEduPersonAddressUnitCalNetDeptName
|
45
|
-
end
|
46
|
-
|
47
|
-
def department_acronym
|
48
|
-
berkeleyEduPersonAddressUnitHRDeptName
|
49
|
-
end
|
50
|
-
|
51
|
-
def directories
|
52
|
-
berkeleyEduPersonAddressPublications
|
53
|
-
end
|
54
|
-
|
55
|
-
# Returns email address associated with this Address.
|
56
|
-
def email
|
57
|
-
mail.first
|
58
|
-
end
|
59
|
-
|
60
|
-
def mail_code
|
61
|
-
berkeleyEduPersonAddressMailCode
|
62
|
-
end
|
63
|
-
|
64
|
-
def mail_release?
|
65
|
-
berkeleyEduEmailRelFlag
|
66
|
-
end
|
67
|
-
|
68
|
-
def phone
|
69
|
-
telephoneNumber.first
|
70
|
-
end
|
71
|
-
|
72
|
-
# Returns postal address as an Array.
|
73
|
-
#
|
74
|
-
# addr.attribute(:postalAddress) #=> '501 Banway Bldg.$Berkeley, CA 94720-3814$USA'
|
75
|
-
# addr.postal_address #=> ['501 Banway Bldg.', 'Berkeley, CA 94720-3814', 'USA']
|
76
|
-
#
|
77
|
-
def postal_address
|
78
|
-
postalAddress == [] ? nil : postalAddress.split("$")
|
79
|
-
end
|
80
|
-
|
81
|
-
def sort_order
|
82
|
-
berkeleyEduPersonAddressSortOrder.first || 0
|
83
|
-
end
|
84
|
-
|
85
|
-
def state
|
86
|
-
st.first
|
87
|
-
end
|
88
|
-
|
89
|
-
def zip
|
90
|
-
postalCode
|
91
|
-
end
|
92
|
-
|
93
|
-
class << self
|
94
|
-
# Returns an Array of Address for <tt>uid</tt>, sorted by sort_order().
|
95
|
-
# Returns an empty Array ([]) if nothing is found.
|
96
|
-
#
|
97
|
-
def find_by_uid(uid)
|
98
|
-
base = "uid=#{uid},ou=people,dc=berkeley,dc=edu"
|
99
|
-
filter = Net::LDAP::Filter.eq("objectclass", 'berkeleyEduPersonAddress')
|
100
|
-
search(:base => base, :filter => filter).sort_by{|addr| addr.sort_order}
|
101
|
-
end
|
102
|
-
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
1
|
+
|
2
|
+
module UCB
|
3
|
+
module LDAP
|
4
|
+
# = UCB::LDAP::Address
|
5
|
+
#
|
6
|
+
# This class models a person address instance in the UCB LDAP directory.
|
7
|
+
#
|
8
|
+
# a = Address.find_by_uid("1234") #=> [#<UCB::LDAP::Address: ...>, ...]
|
9
|
+
#
|
10
|
+
# Addresses are usually loaded through a Person instance:
|
11
|
+
#
|
12
|
+
# p = Person.find_by_uid("1234") #=> #<UCB::LDAP::Person: ...>
|
13
|
+
# addrs = p.addresses #=> [#<UCB::LDAP::Address: ...>, ...]
|
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 Address search.
|
19
|
+
#
|
20
|
+
class Address < Entry
|
21
|
+
@entity_name = 'personAddress'
|
22
|
+
|
23
|
+
def primary_work_address?
|
24
|
+
berkeleyEduPersonAddressPrimaryFlag
|
25
|
+
end
|
26
|
+
|
27
|
+
def address_type
|
28
|
+
berkeleyEduPersonAddressType
|
29
|
+
end
|
30
|
+
|
31
|
+
def building_code
|
32
|
+
berkeleyEduPersonAddressBuildingCode
|
33
|
+
end
|
34
|
+
|
35
|
+
def city
|
36
|
+
l.first
|
37
|
+
end
|
38
|
+
|
39
|
+
def country_code
|
40
|
+
berkeleyEduPersonAddressCountryCode
|
41
|
+
end
|
42
|
+
|
43
|
+
def department_name
|
44
|
+
berkeleyEduPersonAddressUnitCalNetDeptName
|
45
|
+
end
|
46
|
+
|
47
|
+
def department_acronym
|
48
|
+
berkeleyEduPersonAddressUnitHRDeptName
|
49
|
+
end
|
50
|
+
|
51
|
+
def directories
|
52
|
+
berkeleyEduPersonAddressPublications
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns email address associated with this Address.
|
56
|
+
def email
|
57
|
+
mail.first
|
58
|
+
end
|
59
|
+
|
60
|
+
def mail_code
|
61
|
+
berkeleyEduPersonAddressMailCode
|
62
|
+
end
|
63
|
+
|
64
|
+
def mail_release?
|
65
|
+
berkeleyEduEmailRelFlag
|
66
|
+
end
|
67
|
+
|
68
|
+
def phone
|
69
|
+
telephoneNumber.first
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns postal address as an Array.
|
73
|
+
#
|
74
|
+
# addr.attribute(:postalAddress) #=> '501 Banway Bldg.$Berkeley, CA 94720-3814$USA'
|
75
|
+
# addr.postal_address #=> ['501 Banway Bldg.', 'Berkeley, CA 94720-3814', 'USA']
|
76
|
+
#
|
77
|
+
def postal_address
|
78
|
+
postalAddress == [] ? nil : postalAddress.split("$")
|
79
|
+
end
|
80
|
+
|
81
|
+
def sort_order
|
82
|
+
berkeleyEduPersonAddressSortOrder.first || 0
|
83
|
+
end
|
84
|
+
|
85
|
+
def state
|
86
|
+
st.first
|
87
|
+
end
|
88
|
+
|
89
|
+
def zip
|
90
|
+
postalCode
|
91
|
+
end
|
92
|
+
|
93
|
+
class << self
|
94
|
+
# Returns an Array of Address for <tt>uid</tt>, sorted by sort_order().
|
95
|
+
# Returns an empty Array ([]) if nothing is found.
|
96
|
+
#
|
97
|
+
def find_by_uid(uid)
|
98
|
+
base = "uid=#{uid},ou=people,dc=berkeley,dc=edu"
|
99
|
+
filter = Net::LDAP::Filter.eq("objectclass", 'berkeleyEduPersonAddress')
|
100
|
+
search(:base => base, :filter => filter).sort_by{|addr| addr.sort_order}
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
module UCB
|
3
3
|
module LDAP
|
4
4
|
# = UCB::LDAP::Affiliation
|
5
|
-
#
|
5
|
+
#
|
6
6
|
# This class models a persons affiliate entries in the UCB LDAP directory.
|
7
7
|
#
|
8
8
|
# affiliations = Affiliation.find_by_uid("1234") #=> [#<UCB::LDAP::Affiliation: ...>, ...]
|
@@ -13,7 +13,7 @@ module UCB
|
|
13
13
|
# affs = p.affiliations #=> [#<UCB::LDAP::Affiliation: ...>, ...]
|
14
14
|
#
|
15
15
|
# == Note on Binds
|
16
|
-
#
|
16
|
+
#
|
17
17
|
# You must have a privileged bind and pass your credentials to UCB::LDAP.authenticate()
|
18
18
|
# before performing your Affiliation search.
|
19
19
|
#
|
@@ -23,19 +23,19 @@ module UCB
|
|
23
23
|
def create_datetime
|
24
24
|
berkeleyEduAffCreateDate
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
def expired_by
|
28
28
|
berkeleyEduAffExpBy
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
def expiration_date
|
32
32
|
UCB::LDAP.local_date_parse(berkeleyEduAffExpDate)
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
def affiliate_id
|
36
36
|
berkeleyEduAffID.first
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
def affiliate_type
|
40
40
|
berkeleyEduAffType
|
41
41
|
end
|
@@ -43,42 +43,42 @@ module UCB
|
|
43
43
|
def first_name
|
44
44
|
givenName.first
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
def middle_name
|
48
48
|
berkeleyEduMiddleName
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
def last_name
|
52
52
|
sn.first
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
def modified_by
|
56
56
|
berkeleyEduModifiedBy
|
57
57
|
end
|
58
|
-
|
58
|
+
|
59
59
|
def source
|
60
60
|
berkeleyEduPersonAffiliateSource
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
63
|
def dept_code
|
64
64
|
departmentNumber.first
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
def dept_name
|
68
68
|
berkeleyEduUnitCalNetDeptName
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
class << self
|
72
72
|
# Returns an Array of Affiliation for <tt>uid</tt>.
|
73
|
-
# Returns an empty Array ([]) if nothing is found.
|
73
|
+
# Returns an empty Array ([]) if nothing is found.
|
74
74
|
#
|
75
75
|
def find_by_uid(uid)
|
76
76
|
base = "uid=#{uid},ou=people,dc=berkeley,dc=edu"
|
77
77
|
filter = Net::LDAP::Filter.eq("objectclass", 'berkeleyEduPersonAffiliate')
|
78
78
|
search(:base => base, :filter => filter)
|
79
79
|
end
|
80
|
-
|
80
|
+
|
81
81
|
end
|
82
82
|
end
|
83
83
|
end
|
84
|
-
end
|
84
|
+
end
|
@@ -1,448 +1,455 @@
|
|
1
|
-
module UCB
|
2
|
-
module LDAP
|
3
|
-
##
|
4
|
-
# = UCB::LDAP::Entry
|
5
|
-
#
|
6
|
-
# Abstract class representing an entry in the UCB LDAP directory. You
|
7
|
-
# won't ever deal with Entry instances, but instead instances of Entry
|
8
|
-
# sub-classes.
|
9
|
-
#
|
10
|
-
# == Accessing LDAP Attributes
|
11
|
-
#
|
12
|
-
# You will not see the attributes documented in the
|
13
|
-
# instance method section of the documentation for Entry sub-classes,
|
14
|
-
# even though you can access them as
|
15
|
-
# if they _were_ instance methods.
|
16
|
-
#
|
17
|
-
# person = Person.find_by_uid("123") #=> #<UCB::LDAP::Person ..>
|
18
|
-
# people.givenname #=> ["John"]
|
19
|
-
#
|
20
|
-
# Entry sub-classes may have convenience methods that
|
21
|
-
# allow for accessing attributes by friendly names:
|
22
|
-
#
|
23
|
-
# person = Person.person_by_uid("123") #=> #<UCB::LDAP::Person ..>
|
24
|
-
# person.firstname #=> "John"
|
25
|
-
#
|
26
|
-
# See the sub-class documentation for specifics.
|
27
|
-
#
|
28
|
-
# ===Single- / Multi-Value Attributes
|
29
|
-
#
|
30
|
-
# Attribute values are returned as arrays or scalars based on how
|
31
|
-
# they are defined in the LDAP schema.
|
32
|
-
#
|
33
|
-
# Entry subclasses may have convenience
|
34
|
-
# methods that return scalars even though the schema defines
|
35
|
-
# the unerlying attribute as multi-valued becuase in practice the are single-valued.
|
36
|
-
#
|
37
|
-
# === Attribute Types
|
38
|
-
#
|
39
|
-
# Attribute values are stored as arrays of strings in LDAP, but
|
40
|
-
# when accessed through Entry sub-class methods are returned
|
41
|
-
# cast to their Ruby type as defined in the schema. Types are one of:
|
42
|
-
#
|
43
|
-
# * string
|
44
|
-
# * integer
|
45
|
-
# * boolean
|
46
|
-
# * datetime
|
47
|
-
#
|
48
|
-
# === Missing Attribute Values
|
49
|
-
#
|
50
|
-
# If an attribute value is not present, the value returned depends on
|
51
|
-
# type and multi/single value field:
|
52
|
-
#
|
53
|
-
# * empty multi-valued attributes return an empty array ([])
|
54
|
-
# * empty booleans return +false+
|
55
|
-
# * everything else returns +nil+ if empty
|
56
|
-
#
|
57
|
-
# Attempting to get or set an attribute value for an invalid attriubte name
|
58
|
-
# will raise a BadAttributeNameException.
|
59
|
-
#
|
60
|
-
# == Updating LDAP
|
61
|
-
#
|
62
|
-
# If your bind has privleges for updating the directory you can update
|
63
|
-
# the directory using methods of Entry sub-classes. Make sure you call
|
64
|
-
# UCB::LDAP.authenticate before calling any update methods.
|
65
|
-
#
|
66
|
-
# There are three pairs of update methods that behave like Rails ActiveRecord
|
67
|
-
# methods of the same name. These methods are fairly thin wrappers around
|
68
|
-
# standard LDAP update commands.
|
69
|
-
#
|
70
|
-
# The "bang" methods (those ending in "!") differ from their bangless
|
71
|
-
# counterparts in that the bang methods raise +DirectoryNotUpdatedException+
|
72
|
-
# on failure, while the bangless return +false+.
|
73
|
-
#
|
74
|
-
# * #create/#create! - class methods that do LDAP add
|
75
|
-
# * #update_attributes/#update_attributes! - instance methods that do LDAP modify
|
76
|
-
# * #delete/#delete! - instance methods that do LDAP delete
|
77
|
-
#
|
78
|
-
class Entry
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
#
|
100
|
-
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
# this
|
105
|
-
#
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
#
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
#
|
124
|
-
#
|
125
|
-
#
|
126
|
-
#
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
#
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
#
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
#
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
#
|
167
|
-
#
|
168
|
-
#
|
169
|
-
#
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
#
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
#
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
schema_attribute.
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
#
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
#
|
235
|
-
#
|
236
|
-
#
|
237
|
-
#
|
238
|
-
#
|
239
|
-
#
|
240
|
-
#
|
241
|
-
#
|
242
|
-
#
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
end
|
265
|
-
|
266
|
-
##
|
267
|
-
#
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
#
|
274
|
-
#
|
275
|
-
#
|
276
|
-
#
|
277
|
-
#
|
278
|
-
#
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
#
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
end
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
#
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
#
|
327
|
-
#
|
328
|
-
#
|
329
|
-
#
|
330
|
-
#
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
#
|
369
|
-
#
|
370
|
-
#
|
371
|
-
# See
|
372
|
-
#
|
373
|
-
#
|
374
|
-
#
|
375
|
-
#
|
376
|
-
#
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
#
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
##
|
414
|
-
#
|
415
|
-
#
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
1
|
+
module UCB
|
2
|
+
module LDAP
|
3
|
+
##
|
4
|
+
# = UCB::LDAP::Entry
|
5
|
+
#
|
6
|
+
# Abstract class representing an entry in the UCB LDAP directory. You
|
7
|
+
# won't ever deal with Entry instances, but instead instances of Entry
|
8
|
+
# sub-classes.
|
9
|
+
#
|
10
|
+
# == Accessing LDAP Attributes
|
11
|
+
#
|
12
|
+
# You will not see the attributes documented in the
|
13
|
+
# instance method section of the documentation for Entry sub-classes,
|
14
|
+
# even though you can access them as
|
15
|
+
# if they _were_ instance methods.
|
16
|
+
#
|
17
|
+
# person = Person.find_by_uid("123") #=> #<UCB::LDAP::Person ..>
|
18
|
+
# people.givenname #=> ["John"]
|
19
|
+
#
|
20
|
+
# Entry sub-classes may have convenience methods that
|
21
|
+
# allow for accessing attributes by friendly names:
|
22
|
+
#
|
23
|
+
# person = Person.person_by_uid("123") #=> #<UCB::LDAP::Person ..>
|
24
|
+
# person.firstname #=> "John"
|
25
|
+
#
|
26
|
+
# See the sub-class documentation for specifics.
|
27
|
+
#
|
28
|
+
# ===Single- / Multi-Value Attributes
|
29
|
+
#
|
30
|
+
# Attribute values are returned as arrays or scalars based on how
|
31
|
+
# they are defined in the LDAP schema.
|
32
|
+
#
|
33
|
+
# Entry subclasses may have convenience
|
34
|
+
# methods that return scalars even though the schema defines
|
35
|
+
# the unerlying attribute as multi-valued becuase in practice the are single-valued.
|
36
|
+
#
|
37
|
+
# === Attribute Types
|
38
|
+
#
|
39
|
+
# Attribute values are stored as arrays of strings in LDAP, but
|
40
|
+
# when accessed through Entry sub-class methods are returned
|
41
|
+
# cast to their Ruby type as defined in the schema. Types are one of:
|
42
|
+
#
|
43
|
+
# * string
|
44
|
+
# * integer
|
45
|
+
# * boolean
|
46
|
+
# * datetime
|
47
|
+
#
|
48
|
+
# === Missing Attribute Values
|
49
|
+
#
|
50
|
+
# If an attribute value is not present, the value returned depends on
|
51
|
+
# type and multi/single value field:
|
52
|
+
#
|
53
|
+
# * empty multi-valued attributes return an empty array ([])
|
54
|
+
# * empty booleans return +false+
|
55
|
+
# * everything else returns +nil+ if empty
|
56
|
+
#
|
57
|
+
# Attempting to get or set an attribute value for an invalid attriubte name
|
58
|
+
# will raise a BadAttributeNameException.
|
59
|
+
#
|
60
|
+
# == Updating LDAP
|
61
|
+
#
|
62
|
+
# If your bind has privleges for updating the directory you can update
|
63
|
+
# the directory using methods of Entry sub-classes. Make sure you call
|
64
|
+
# UCB::LDAP.authenticate before calling any update methods.
|
65
|
+
#
|
66
|
+
# There are three pairs of update methods that behave like Rails ActiveRecord
|
67
|
+
# methods of the same name. These methods are fairly thin wrappers around
|
68
|
+
# standard LDAP update commands.
|
69
|
+
#
|
70
|
+
# The "bang" methods (those ending in "!") differ from their bangless
|
71
|
+
# counterparts in that the bang methods raise +DirectoryNotUpdatedException+
|
72
|
+
# on failure, while the bangless return +false+.
|
73
|
+
#
|
74
|
+
# * #create/#create! - class methods that do LDAP add
|
75
|
+
# * #update_attributes/#update_attributes! - instance methods that do LDAP modify
|
76
|
+
# * #delete/#delete! - instance methods that do LDAP delete
|
77
|
+
#
|
78
|
+
class Entry
|
79
|
+
TESTING = false
|
80
|
+
|
81
|
+
##
|
82
|
+
# Returns new instance of UCB::LDAP::Entry. The argument
|
83
|
+
# net_ldap_entry is an instance of Net::LDAP::Entry.
|
84
|
+
#
|
85
|
+
# You should not need to create any UCB::LDAP::Entry instances;
|
86
|
+
# they are created by calls to UCB::LDAP.search and friends.
|
87
|
+
#
|
88
|
+
def initialize(net_ldap_entry) #:nodoc:
|
89
|
+
# Don't store Net::LDAP entry in object since it uses the block
|
90
|
+
# initialization method of Hash which can't be marshalled ... this
|
91
|
+
# means it can't be stored in a Rails session.
|
92
|
+
@attributes = {}
|
93
|
+
net_ldap_entry.each do |attr, value|
|
94
|
+
@attributes[canonical(attr)] = value.map { |v| v.dup }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# <tt>Hash</tt> of attributes returned from underlying NET::LDAP::Entry
|
100
|
+
# instance. Hash keys are #canonical attribute names, hash values are attribute
|
101
|
+
# values <em>as returned from LDAP</em>, i.e. arrays.
|
102
|
+
#
|
103
|
+
# You should most likely be referencing attributes as if they were
|
104
|
+
# instance methods rather than directly through this method. See top of
|
105
|
+
# this document.
|
106
|
+
#
|
107
|
+
def attributes
|
108
|
+
@attributes
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# Returns the value of the <em>Distinguished Name</em> attribute.
|
113
|
+
#
|
114
|
+
def dn
|
115
|
+
attributes[canonical(:dn)]
|
116
|
+
end
|
117
|
+
|
118
|
+
def canonical(string_or_symbol) #:nodoc:
|
119
|
+
self.class.canonical(string_or_symbol)
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# Update an existing entry. Returns entry if successful else false.
|
124
|
+
#
|
125
|
+
# attrs = {:attr1 => "new_v1", :attr2 => "new_v2"}
|
126
|
+
# entry.update_attributes(attrs)
|
127
|
+
#
|
128
|
+
def update_attributes(attrs)
|
129
|
+
attrs.each { |k, v| self.send("#{k}=", v) }
|
130
|
+
if modify
|
131
|
+
@attributes = self.class.find_by_dn(dn).attributes.dup
|
132
|
+
return true
|
133
|
+
end
|
134
|
+
false
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Same as #update_attributes(), but raises DirectoryNotUpdated on failure.
|
139
|
+
#
|
140
|
+
def update_attributes!(attrs)
|
141
|
+
update_attributes(attrs) || raise(DirectoryNotUpdatedException)
|
142
|
+
end
|
143
|
+
|
144
|
+
##
|
145
|
+
# Delete entry. Returns +true+ on sucess, +false+ on failure.
|
146
|
+
#
|
147
|
+
def delete
|
148
|
+
net_ldap.delete(:dn => dn)
|
149
|
+
end
|
150
|
+
|
151
|
+
##
|
152
|
+
# Same as #delete() except raises DirectoryNotUpdated on failure.
|
153
|
+
#
|
154
|
+
def delete!
|
155
|
+
delete || raise(DirectoryNotUpdatedException)
|
156
|
+
end
|
157
|
+
|
158
|
+
def net_ldap
|
159
|
+
self.class.net_ldap
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
#private unless TESTING
|
164
|
+
|
165
|
+
##
|
166
|
+
# Used to get/set attribute values.
|
167
|
+
#
|
168
|
+
# If we can't make an attribute name out of method, let
|
169
|
+
# regular method_missing() handle it.
|
170
|
+
#
|
171
|
+
def method_missing(method, *args) #:nodoc:
|
172
|
+
setter_method?(method) ? value_setter(method, *args) : value_getter(method)
|
173
|
+
rescue BadAttributeNameException
|
174
|
+
return super
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Returns +true+ if _method_ is a "setter", i.e., ends in "=".
|
179
|
+
#
|
180
|
+
def setter_method?(method)
|
181
|
+
method.to_s[-1, 1] == "="
|
182
|
+
end
|
183
|
+
|
184
|
+
##
|
185
|
+
# Called by method_missing() to get an attribute value.
|
186
|
+
#
|
187
|
+
def value_getter(method)
|
188
|
+
schema_attribute = self.class.schema_attribute(method)
|
189
|
+
raw_value = attributes[canonical(schema_attribute.name)]
|
190
|
+
schema_attribute.get_value(raw_value)
|
191
|
+
end
|
192
|
+
|
193
|
+
##
|
194
|
+
# Called by method_missing() to set an attribute value.
|
195
|
+
#
|
196
|
+
def value_setter(method, *args)
|
197
|
+
schema_attribute = self.class.schema_attribute(method.to_s.chop)
|
198
|
+
attr_key = canonical(schema_attribute.name)
|
199
|
+
assigned_attributes[attr_key] = schema_attribute.ldap_value(args[0])
|
200
|
+
end
|
201
|
+
|
202
|
+
def assigned_attributes
|
203
|
+
@assigned_attributes ||= {}
|
204
|
+
end
|
205
|
+
|
206
|
+
def modify_operations
|
207
|
+
ops = []
|
208
|
+
assigned_attributes.keys.sort_by { |k| k.to_s }.each do |key|
|
209
|
+
value = assigned_attributes[key]
|
210
|
+
op = value.nil? ? :delete : :replace
|
211
|
+
ops << [op, key, value]
|
212
|
+
end
|
213
|
+
ops
|
214
|
+
end
|
215
|
+
|
216
|
+
def modify()
|
217
|
+
if UCB::LDAP.net_ldap.modify(:dn => dn, :operations => modify_operations)
|
218
|
+
@assigned_attributes = nil
|
219
|
+
return true
|
220
|
+
end
|
221
|
+
false
|
222
|
+
end
|
223
|
+
|
224
|
+
# Class methods
|
225
|
+
class << self
|
226
|
+
|
227
|
+
public
|
228
|
+
|
229
|
+
def filter_in(attribute_name, array_of_values)
|
230
|
+
filters = array_of_values.map { |value| Net::LDAP::Filter.eq(attribute_name, value) }
|
231
|
+
UCB::LDAP::Entry.combine_filters(filters, '|')
|
232
|
+
end
|
233
|
+
|
234
|
+
# Creates and returns new entry. Returns +false+ if unsuccessful.
|
235
|
+
# Sets :objectclass key of <em>args[:attributes]</em> to
|
236
|
+
# object_classes read from schema.
|
237
|
+
#
|
238
|
+
# dn = "uid=999999,ou=people,dc=example,dc=com"
|
239
|
+
# attr = {
|
240
|
+
# :uid => "999999",
|
241
|
+
# :mail => "gsmith@example.com"
|
242
|
+
# }
|
243
|
+
#
|
244
|
+
# EntrySubClass.create(:dn => dn, :attributes => attr) #=> #<UCB::LDAP::EntrySubClass ..>
|
245
|
+
#
|
246
|
+
# Caller is responsible for setting :dn and :attributes correctly,
|
247
|
+
# as well as any other validation.
|
248
|
+
#
|
249
|
+
def create(args)
|
250
|
+
args[:attributes][:objectclass] = object_classes
|
251
|
+
result = net_ldap.add(args)
|
252
|
+
result or return false
|
253
|
+
find_by_dn(args[:dn])
|
254
|
+
end
|
255
|
+
|
256
|
+
##
|
257
|
+
# Returns entry whose distinguised name is _dn_.
|
258
|
+
def find_by_dn(dn)
|
259
|
+
search(
|
260
|
+
:base => dn,
|
261
|
+
:scope => Net::LDAP::SearchScope_BaseObject,
|
262
|
+
:filter => "objectClass=*"
|
263
|
+
).first
|
264
|
+
end
|
265
|
+
|
266
|
+
##
|
267
|
+
# Same as #create(), but raises DirectoryNotUpdated on failure.
|
268
|
+
def create!(args)
|
269
|
+
create(args) || raise(DirectoryNotUpdatedException)
|
270
|
+
end
|
271
|
+
|
272
|
+
##
|
273
|
+
# Returns a new Net::LDAP::Filter that is the result of combining
|
274
|
+
# <em>filters</em> using <em>operator</em> (<em>filters</em> is
|
275
|
+
# an +Array+ of Net::LDAP::Filter).
|
276
|
+
#
|
277
|
+
# See Net::LDAP#& and Net::LDAP#| for details.
|
278
|
+
#
|
279
|
+
# f1 = Net::LDAP::Filter.eq("lastname", "hansen")
|
280
|
+
# f2 = Net::LDAP::Filter.eq("firstname", "steven")
|
281
|
+
#
|
282
|
+
# combine_filters([f1, f2]) # same as: f1 & f2
|
283
|
+
# combine_filters([f1, f2], '|') # same as: f1 | f2
|
284
|
+
#
|
285
|
+
def combine_filters(filters, operator = '&')
|
286
|
+
filters.inject { |accum, filter| accum.send(operator, filter) }
|
287
|
+
end
|
288
|
+
|
289
|
+
##
|
290
|
+
# Returns Net::LDAP::Filter. Allows for <em>filter</em> to
|
291
|
+
# be a +Hash+ of :key => value. Filters are combined with "&".
|
292
|
+
#
|
293
|
+
# UCB::LDAP::Entry.make_search_filter(:uid => '123')
|
294
|
+
# UCB::LDAP::Entry.make_search_filter(:a1 => v1, :a2 => v2)
|
295
|
+
#
|
296
|
+
def make_search_filter(filter)
|
297
|
+
return filter if filter.instance_of? Net::LDAP::Filter
|
298
|
+
return filter if filter.instance_of? String
|
299
|
+
|
300
|
+
filters = []
|
301
|
+
# sort so result is predictable for unit test
|
302
|
+
filter.keys.sort_by { |symbol| "#{symbol}" }.each do |attr|
|
303
|
+
filters << Net::LDAP::Filter.eq("#{attr}", "#{filter[attr]}")
|
304
|
+
end
|
305
|
+
combine_filters(filters, "&")
|
306
|
+
end
|
307
|
+
|
308
|
+
##
|
309
|
+
# Returns +Array+ of object classes making up this type of LDAP entity.
|
310
|
+
def object_classes
|
311
|
+
@object_classes ||= UCB::LDAP::Schema.schema_hash[entity_name]["objectClasses"]
|
312
|
+
end
|
313
|
+
|
314
|
+
def unique_object_class
|
315
|
+
@unique_object_class ||= UCB::LDAP::Schema.schema_hash[entity_name]["uniqueObjectClass"]
|
316
|
+
end
|
317
|
+
|
318
|
+
##
|
319
|
+
# returns an Array of symbols where each symbol is the name of
|
320
|
+
# a required attribute for the Entry
|
321
|
+
def required_attributes
|
322
|
+
required_schema_attributes.keys
|
323
|
+
end
|
324
|
+
|
325
|
+
##
|
326
|
+
# returns Hash of SchemaAttribute objects that are required
|
327
|
+
# for the Entry. Each SchemaAttribute object is keyed to the
|
328
|
+
# attribute's name.
|
329
|
+
#
|
330
|
+
# Note: required_schema_attributes will not return aliases, it
|
331
|
+
# only returns the original attributes
|
332
|
+
#
|
333
|
+
# Example:
|
334
|
+
# Person.required_schema_attribues[:cn]
|
335
|
+
# => <UCB::LDAP::Schema::Attribute:0x11c6b68>
|
336
|
+
#
|
337
|
+
def required_schema_attributes
|
338
|
+
required_atts = schema_attributes_hash.reject { |key, value| !value.required? }
|
339
|
+
required_atts.reject do |key, value|
|
340
|
+
aliases = value.aliases.map { |a| canonical(a) }
|
341
|
+
aliases.include?(key)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
##
|
346
|
+
# Returns an +Array+ of Schema::Attribute for the entity.
|
347
|
+
#
|
348
|
+
def schema_attributes_array
|
349
|
+
@schema_attributes_array || set_schema_attributes
|
350
|
+
@schema_attributes_array
|
351
|
+
end
|
352
|
+
|
353
|
+
##
|
354
|
+
# Returns as +Hash+ whose keys are the canonical attribute names
|
355
|
+
# and whose values are the corresponding Schema::Attributes.
|
356
|
+
#
|
357
|
+
def schema_attributes_hash
|
358
|
+
@schema_attributes_hash || set_schema_attributes
|
359
|
+
@schema_attributes_hash
|
360
|
+
end
|
361
|
+
|
362
|
+
def schema_attribute(attribute_name)
|
363
|
+
schema_attributes_hash[canonical(attribute_name)] ||
|
364
|
+
raise(BadAttributeNameException, "'#{attribute_name}' is not a recognized attribute name")
|
365
|
+
end
|
366
|
+
|
367
|
+
##
|
368
|
+
# Returns Array of UCB::LDAP::Entry for entries matching _args_.
|
369
|
+
# When called from a subclass, returns Array of subclass instances.
|
370
|
+
#
|
371
|
+
# See Net::LDAP::search for more information on _args_.
|
372
|
+
#
|
373
|
+
# Most common arguments are <tt>:base</tt> and <tt>:filter</tt>.
|
374
|
+
# Search methods of subclasses have default <tt>:base</tt> that
|
375
|
+
# can be overriden.
|
376
|
+
#
|
377
|
+
# See make_search_filter for <tt>:filter</tt> options.
|
378
|
+
#
|
379
|
+
# base = "ou=people,dc=berkeley,dc=edu"
|
380
|
+
# entries = UCB::LDAP::Entry.search(:base => base, :filter => {:uid => '123'})
|
381
|
+
# entries = UCB::LDAP::Entry.search(:base => base, :filter => {:sn => 'Doe', :givenname => 'John'}
|
382
|
+
#
|
383
|
+
def search(args={})
|
384
|
+
args = args.dup
|
385
|
+
args[:base] ||= tree_base
|
386
|
+
args[:filter] = make_search_filter args[:filter] if args[:filter]
|
387
|
+
|
388
|
+
results = []
|
389
|
+
net_ldap.search(args) do |entry|
|
390
|
+
results << new(entry)
|
391
|
+
end
|
392
|
+
results
|
393
|
+
end
|
394
|
+
|
395
|
+
##
|
396
|
+
# Returns the canonical representation of a symbol or string so
|
397
|
+
# we can look up attributes in a number of ways.
|
398
|
+
#
|
399
|
+
def canonical(string_or_symbol)
|
400
|
+
string_or_symbol.to_s.downcase.to_sym
|
401
|
+
end
|
402
|
+
|
403
|
+
##
|
404
|
+
# Returns underlying Net::LDAP instance.
|
405
|
+
#
|
406
|
+
def net_ldap #:nodoc:
|
407
|
+
UCB::LDAP.net_ldap
|
408
|
+
end
|
409
|
+
|
410
|
+
## TODO: restore private
|
411
|
+
# private unless $TESTING
|
412
|
+
|
413
|
+
##
|
414
|
+
# Schema entity name. Set in each subclass.
|
415
|
+
#
|
416
|
+
def entity_name
|
417
|
+
@entity_name
|
418
|
+
end
|
419
|
+
|
420
|
+
##
|
421
|
+
# Want an array of Schema::Attributes as well as a hash
|
422
|
+
# of all possible variations on a name pointing to correct array element.
|
423
|
+
#
|
424
|
+
def set_schema_attributes
|
425
|
+
@schema_attributes_array = []
|
426
|
+
@schema_attributes_hash = {}
|
427
|
+
UCB::LDAP::Schema.schema_hash[entity_name]["attributes"].each do |k, v|
|
428
|
+
sa = UCB::LDAP::Schema::Attribute.new(v.merge("name" => k))
|
429
|
+
@schema_attributes_array << sa
|
430
|
+
[sa.name, sa.aliases].flatten.each do |name|
|
431
|
+
@schema_attributes_hash[canonical(name)] = sa
|
432
|
+
end
|
433
|
+
end
|
434
|
+
rescue
|
435
|
+
raise "Error loading schema attributes for entity_name '#{entity_name}'"
|
436
|
+
end
|
437
|
+
|
438
|
+
##
|
439
|
+
# Returns tree base for LDAP searches. Subclasses each have
|
440
|
+
# their own value.
|
441
|
+
#
|
442
|
+
# Can be overridden in #search by passing in a <tt>:base</tt> parm.
|
443
|
+
##
|
444
|
+
def tree_base
|
445
|
+
@tree_base
|
446
|
+
end
|
447
|
+
|
448
|
+
def tree_base=(tree_base)
|
449
|
+
@tree_base = tree_base
|
450
|
+
end
|
451
|
+
|
452
|
+
end # end of class methods
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|