ucb_ldap 2.0.0.pre1 → 2.0.0.pre3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|