activeldap 4.0.5 → 6.1.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.
- checksums.yaml +5 -5
- data/.yardopts +3 -1
- data/doc/text/development.md +26 -0
- data/doc/text/{news.textile → news.md} +451 -241
- data/doc/text/{rails.textile → rails.md} +44 -33
- data/doc/text/{tutorial.textile → tutorial.md} +177 -185
- data/lib/active_ldap/adapter/base.rb +40 -17
- data/lib/active_ldap/adapter/jndi.rb +21 -9
- data/lib/active_ldap/adapter/jndi_connection.rb +83 -20
- data/lib/active_ldap/adapter/ldap.rb +50 -28
- data/lib/active_ldap/adapter/ldap_ext.rb +32 -13
- data/lib/active_ldap/adapter/net_ldap.rb +26 -24
- data/lib/active_ldap/associations.rb +5 -5
- data/lib/active_ldap/attribute_methods/before_type_cast.rb +1 -1
- data/lib/active_ldap/attribute_methods/dirty.rb +4 -7
- data/lib/active_ldap/attribute_methods/query.rb +1 -1
- data/lib/active_ldap/attribute_methods/read.rb +5 -1
- data/lib/active_ldap/attribute_methods/write.rb +1 -1
- data/lib/active_ldap/attribute_methods.rb +1 -2
- data/lib/active_ldap/base.rb +61 -14
- data/lib/active_ldap/callbacks.rb +7 -8
- data/lib/active_ldap/configuration.rb +27 -3
- data/lib/active_ldap/connection.rb +4 -22
- data/lib/active_ldap/distinguished_name.rb +1 -1
- data/lib/active_ldap/human_readable.rb +5 -4
- data/lib/active_ldap/operations.rb +24 -4
- data/lib/active_ldap/persistence.rb +3 -2
- data/lib/active_ldap/populate.rb +5 -3
- data/lib/active_ldap/railties/controller_runtime.rb +1 -2
- data/lib/active_ldap/schema/syntaxes.rb +8 -4
- data/lib/active_ldap/validations.rb +12 -4
- data/lib/active_ldap/version.rb +1 -1
- data/lib/active_ldap.rb +0 -7
- data/po/en/active-ldap.po +2 -2
- data/po/ja/active-ldap.po +3 -3
- data/test/add-phonetic-attribute-options-to-slapd.ldif +3 -3
- data/test/al-test-utils.rb +125 -38
- data/test/command.rb +13 -16
- data/test/enable-dynamic-groups.ldif +22 -0
- data/test/enable-start-tls.ldif +27 -0
- data/test/run-test.rb +0 -4
- data/test/test_base.rb +223 -22
- data/test/test_base_per_instance.rb +33 -1
- data/test/test_callback.rb +10 -8
- data/test/test_connection.rb +4 -0
- data/test/test_connection_per_class.rb +34 -0
- data/test/test_dn.rb +7 -0
- data/test/test_entry.rb +1 -0
- data/test/test_find.rb +14 -3
- data/test/test_supported_control.rb +1 -1
- data/test/test_syntax.rb +5 -0
- data/test/test_validation.rb +28 -15
- metadata +23 -24
- data/README.textile +0 -141
- data/doc/text/development.textile +0 -54
- data/lib/active_ldap/timeout.rb +0 -75
- data/lib/active_ldap/timeout_stub.rb +0 -17
@@ -1,17 +1,18 @@
|
|
1
|
-
|
1
|
+
# Tutorial
|
2
2
|
|
3
|
-
|
3
|
+
## Introduction
|
4
4
|
|
5
|
-
ActiveLdap is a novel way of interacting with LDAP. Most interaction
|
6
|
-
LDAP is done using clunky LDIFs, web interfaces, or with painful
|
7
|
-
required a thick reference manual nearby. ActiveLdap aims to
|
8
|
-
Inspired by
|
9
|
-
|
5
|
+
ActiveLdap is a novel way of interacting with LDAP. Most interaction
|
6
|
+
with LDAP is done using clunky LDIFs, web interfaces, or with painful
|
7
|
+
APIs that required a thick reference manual nearby. ActiveLdap aims to
|
8
|
+
fix that. Inspired by [Active
|
9
|
+
Record](https://rubygems.org/gems/activerecord), ActiveLdap provides
|
10
|
+
an object oriented interface to LDAP entries.
|
10
11
|
|
11
12
|
The target audience is system administrators and LDAP users everywhere that
|
12
13
|
need quick, clean access to LDAP in Ruby.
|
13
14
|
|
14
|
-
|
15
|
+
### What's LDAP?
|
15
16
|
|
16
17
|
LDAP stands for "Lightweight Directory Access Protocol." Basically this means
|
17
18
|
that it is the protocol used for accessing LDAP servers. LDAP servers
|
@@ -23,10 +24,10 @@ authorization server for Unix systems. (Unfortunately, I've yet to try this
|
|
23
24
|
against Microsoft's ActiveDirectory, despite what the name implies.)
|
24
25
|
|
25
26
|
Further reading:
|
26
|
-
*
|
27
|
-
*
|
27
|
+
* [RFC1777](https://tools.ietf.org/html/rfc1777) - Lightweight Directory Access Protocol
|
28
|
+
* [OpenLDAP](https://www.openldap.org)
|
28
29
|
|
29
|
-
|
30
|
+
### So why use ActiveLdap?
|
30
31
|
|
31
32
|
Using LDAP directly (even with the excellent Ruby/LDAP), leaves you bound to
|
32
33
|
the world of the predefined LDAP API. While this API is important for many
|
@@ -35,70 +36,68 @@ arrays of LDAP.mod entries make code harder to read, less intuitive, and just
|
|
35
36
|
less fun to write. Hopefully, ActiveLdap will remedy all of these
|
36
37
|
problems!
|
37
38
|
|
38
|
-
|
39
|
+
## Getting Started
|
39
40
|
|
40
|
-
|
41
|
+
### Requirements
|
41
42
|
|
42
|
-
* A Ruby implementation:
|
43
|
-
* A LDAP library:
|
44
|
-
* A LDAP server:
|
45
|
-
|
43
|
+
* A Ruby implementation: [Ruby](https://www.ruby-lang.org) or [JRuby](https://www.jruby.org/)
|
44
|
+
* A LDAP library: [Ruby/LDAP](https://rubygems.org/gems/ruby-ldap) (for Ruby), [Net::LDAP](https://rubygems.org/gems/net-ldap) (for Ruby or JRuby) or JNDI (for JRuby)
|
45
|
+
* A LDAP server: [OpenLDAP](https://www.openldap.org/), etc
|
46
|
+
* Your LDAP server must allow `root_dse` queries to allow for schema queries
|
46
47
|
|
47
|
-
|
48
|
+
### Installation
|
48
49
|
|
49
50
|
Assuming all the requirements are installed, you can install by gem.
|
50
51
|
|
51
|
-
|
52
|
-
!!!plain
|
52
|
+
```console
|
53
53
|
# gem install activeldap
|
54
|
-
|
54
|
+
```
|
55
55
|
|
56
56
|
Now as a quick test, you can run:
|
57
57
|
|
58
|
-
|
59
|
-
$ irb
|
58
|
+
```console
|
59
|
+
$ irb
|
60
60
|
irb> require 'active_ldap'
|
61
61
|
=> true
|
62
62
|
irb> exit
|
63
|
-
|
63
|
+
```
|
64
64
|
|
65
65
|
If the require returns false or an exception is raised, there has been a
|
66
|
-
problem with the installation.
|
67
|
-
install.
|
66
|
+
problem with the installation.
|
68
67
|
|
69
|
-
|
68
|
+
## Usage
|
70
69
|
|
71
70
|
This section covers using ActiveLdap from writing extension classes to
|
72
71
|
writing applications that use them.
|
73
72
|
|
74
73
|
Just to give a taste of what's to come, here is a quick example using irb:
|
75
74
|
|
76
|
-
|
75
|
+
```text
|
77
76
|
irb> require 'active_ldap'
|
78
|
-
|
77
|
+
```
|
79
78
|
|
80
79
|
Call setup_connection method for connect to LDAP server. In this case, LDAP server
|
81
80
|
is localhost, and base of LDAP tree is "dc=dataspill,dc=org".
|
82
81
|
|
83
|
-
|
82
|
+
```text
|
84
83
|
irb> ActiveLdap::Base.setup_connection :host => 'localhost', :base => 'dc=dataspill,dc=org'
|
85
|
-
|
84
|
+
```
|
86
85
|
|
87
86
|
Here's an extension class that maps to the LDAP Group objects:
|
88
87
|
|
89
|
-
|
88
|
+
```text
|
90
89
|
irb> class Group < ActiveLdap::Base
|
91
90
|
irb* ldap_mapping
|
92
91
|
irb* end
|
93
|
-
|
92
|
+
```
|
94
93
|
|
95
|
-
In the above code, Group class handles sub tree of ou=Groups
|
96
|
-
|
97
|
-
of Group class represents a LDAP object under ou=
|
94
|
+
In the above code, Group class handles sub tree of `ou=Groups`
|
95
|
+
that is `:base` value specified by setup_connection. A instance
|
96
|
+
of Group class represents a LDAP object under `ou=Groups`.
|
98
97
|
|
99
98
|
Here is the Group class in use:
|
100
99
|
|
101
|
-
|
100
|
+
```text
|
102
101
|
# Get all group names
|
103
102
|
irb> all_groups = Group.find(:all, '*').collect {|group| group.cn}
|
104
103
|
=> ["root", "daemon", "bin", "sys", "adm", "tty", ..., "develop"]
|
@@ -114,48 +113,47 @@ irb> group.cn
|
|
114
113
|
# Get gid_number of the develop group
|
115
114
|
irb> group.gid_number
|
116
115
|
=> "1003"
|
117
|
-
|
116
|
+
```
|
118
117
|
|
119
118
|
That's it! No let's get back in to it.
|
120
119
|
|
121
|
-
|
120
|
+
### Extension Classes
|
122
121
|
|
123
122
|
Extension classes are classes that are subclassed from ActiveLdap::Base. They
|
124
123
|
are used to represent objects in your LDAP server abstractly.
|
125
124
|
|
126
|
-
|
125
|
+
#### Why do I need them?
|
127
126
|
|
128
127
|
Extension classes are what make ActiveLdap "active"! They do all the
|
129
128
|
background work to make easy-to-use objects by mapping the LDAP object's
|
130
129
|
attributes on to a Ruby class.
|
131
130
|
|
132
131
|
|
133
|
-
|
132
|
+
#### Special Methods
|
134
133
|
|
135
134
|
I will briefly talk about each of the methods you can use when defining an
|
136
135
|
extension class. In the above example, I only made one special method call
|
137
136
|
inside the Group class. More than likely, you will want to more than that.
|
138
137
|
|
139
|
-
|
138
|
+
##### `ldap_mapping`
|
140
139
|
|
141
140
|
ldap_mapping is the only required method to setup an extension class for use
|
142
141
|
with ActiveLdap. It must be called inside of a subclass as shown above.
|
143
142
|
|
144
143
|
Below is a much more realistic Group class:
|
145
144
|
|
146
|
-
|
145
|
+
```ruby
|
147
146
|
class Group < ActiveLdap::Base
|
148
147
|
ldap_mapping :dn_attribute => 'cn',
|
149
148
|
:prefix => 'ou=Groups', :classes => ['top', 'posixGroup'],
|
150
149
|
:scope => :one
|
151
150
|
end
|
152
|
-
|
151
|
+
```
|
153
152
|
|
154
153
|
As you can see, this method is used for defining how this class maps in to LDAP. Let's say that
|
155
154
|
my LDAP tree looks something like this:
|
156
155
|
|
157
|
-
|
158
|
-
!!!plain
|
156
|
+
```text
|
159
157
|
* dc=dataspill,dc=org
|
160
158
|
|- ou=People,dc=dataspill,dc=org
|
161
159
|
|+ ou=Groups,dc=dataspill,dc=org
|
@@ -163,26 +161,25 @@ my LDAP tree looks something like this:
|
|
163
161
|
|- cn=develop,ou=Groups,dc=dataspill,dc=org
|
164
162
|
|- cn=root,ou=Groups,dc=dataspill,dc=org
|
165
163
|
|- ...
|
166
|
-
|
164
|
+
```
|
167
165
|
|
168
166
|
Under ou=People I store user objects, and under ou=Groups, I store group
|
169
|
-
objects. What
|
170
|
-
abstractly. With the given
|
171
|
-
entries under ou=Groups,dc=dataspill,dc=org using the primary attribute 'cn'
|
167
|
+
objects. What `ldap_mapping` has done is mapped the class in to the LDAP tree
|
168
|
+
abstractly. With the given `:dn_attributes` and `:prefix`, it will only work for
|
169
|
+
entries under `ou=Groups,dc=dataspill,dc=org` using the primary attribute 'cn'
|
172
170
|
as the beginning of the distinguished name.
|
173
171
|
|
174
172
|
Just for clarity, here's how the arguments map out:
|
175
173
|
|
176
|
-
|
177
|
-
!!!plain
|
174
|
+
```text
|
178
175
|
cn=develop,ou=Groups,dc=dataspill,dc=org
|
179
176
|
^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
|
180
177
|
:dn_attribute | |
|
181
178
|
:prefix |
|
182
179
|
:base from setup_connection
|
183
|
-
|
180
|
+
```
|
184
181
|
|
185
|
-
|
182
|
+
`:scope` tells ActiveLdap to only search under ou=Groups, and not to look deeper
|
186
183
|
for dn_attribute matches.
|
187
184
|
(e.g. cn=develop,ou=DevGroups,ou=Groups,dc=dataspill,dc=org)
|
188
185
|
You can choose value from between :sub, :one and :base.
|
@@ -209,46 +206,45 @@ case, it would be 'ou=Groups'.
|
|
209
206
|
:prefix.
|
210
207
|
|
211
208
|
|
212
|
-
|
209
|
+
##### `belongs_to`
|
213
210
|
|
214
211
|
This method allows an extension class to make use of other extension classes
|
215
212
|
tying objects together across the LDAP tree. Often, user objects will be
|
216
213
|
members of, or belong_to, Group objects.
|
217
214
|
|
218
|
-
|
219
|
-
!!!plain
|
215
|
+
```text
|
220
216
|
* dc=dataspill,dc=org
|
221
217
|
|+ ou=People,dc=dataspill,dc=org
|
222
218
|
\
|
223
219
|
|- uid=drewry,ou=People,dc=dataspill,dc=org
|
224
220
|
|- ou=Groups,dc=dataspill,dc=org
|
225
|
-
|
221
|
+
```
|
226
222
|
|
227
223
|
|
228
224
|
In the above tree, one such example would be user 'drewry' who is a part of the
|
229
225
|
group 'develop'. You can see this by looking at the 'memberUid' field of 'develop'.
|
230
226
|
|
231
|
-
|
227
|
+
```text
|
232
228
|
irb> develop = Group.find('develop')
|
233
229
|
=> ...
|
234
230
|
irb> develop.memberUid
|
235
231
|
=> ['drewry', 'builder']
|
236
|
-
|
232
|
+
```
|
237
233
|
|
238
234
|
If we look at the LDAP entry for 'drewry', we do not see any references to
|
239
235
|
group 'develop'. In order to remedy that, we can use belongs_to
|
240
236
|
|
241
|
-
|
237
|
+
```text
|
242
238
|
irb> class User < ActiveLdap::Base
|
243
239
|
irb* ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People', :classes => ['top','account']
|
244
240
|
irb* belongs_to :groups, :class_name => 'Group', :many => 'memberUid', :foreign_key => 'uid'
|
245
241
|
irb* end
|
246
|
-
|
242
|
+
```
|
247
243
|
|
248
244
|
Now, class User will have a method called 'groups' which will retrieve all
|
249
245
|
Group objects that a user is in.
|
250
246
|
|
251
|
-
|
247
|
+
```text
|
252
248
|
irb> me = User.find('drewry')
|
253
249
|
irb> me.groups
|
254
250
|
=> #<ActiveLdap::Association::BelongsToMany...> # Enumerable object
|
@@ -258,22 +254,22 @@ irb> me.groups.each { |group| p group.cn };nil
|
|
258
254
|
"develop"
|
259
255
|
=> nil
|
260
256
|
(Note: nil is just there to make the output cleaner...)
|
261
|
-
|
257
|
+
```
|
262
258
|
|
263
259
|
TIP: If you weren't sure what the distinguished name attribute was for Group,
|
264
260
|
you could also do the following:
|
265
261
|
|
266
|
-
|
262
|
+
```text
|
267
263
|
irb> me.groups.each { |group| p group.id };nil
|
268
264
|
"cdrom"
|
269
265
|
"audio"
|
270
266
|
"develop"
|
271
267
|
=> nil
|
272
|
-
|
268
|
+
```
|
273
269
|
|
274
270
|
Now let's talk about the arguments of belongs_to. We use the following code that extends Group group a bit for explain:
|
275
271
|
|
276
|
-
|
272
|
+
```ruby
|
277
273
|
class User < ActiveLdap::Base
|
278
274
|
ldap_mapping :dn_attribute => 'uid', :prefix => 'People', :classes => ['top','account']
|
279
275
|
|
@@ -285,42 +281,42 @@ class User < ActiveLdap::Base
|
|
285
281
|
belongs_to :groups, :foreign_key => 'uid',
|
286
282
|
:class_name => 'Group', :many => 'memberUid',
|
287
283
|
end
|
288
|
-
|
284
|
+
```
|
289
285
|
|
290
286
|
The first argument is the name of the method you wish to create. In this case, we created a method called primary_group and groups using the symbol :primary_group and :groups. The next collection of arguments are actually a Hash (as with ldap_mapping).
|
291
287
|
|
292
|
-
|
288
|
+
`:foreign_key` tells `belongs_to` what attribute Group objects have that match the related object's attribute. If `:foreign_key` is left off of the argument list, it is assumed to be the dn_attribute.
|
293
289
|
|
294
290
|
In the example, uid is used for :foreign_key. It may confuse you.
|
295
291
|
|
296
|
-
ActiveLdap uses
|
297
|
-
may not be "foreign key". You can consider
|
292
|
+
ActiveLdap uses `:foreign_key` as "own attribute name". So it
|
293
|
+
may not be "foreign key". You can consider `:foreign_key` just
|
298
294
|
as a relation key.
|
299
295
|
|
300
|
-
|
296
|
+
`:primary_key` is treated as "related object's attribute name"
|
301
297
|
as we discussed later.
|
302
298
|
|
303
|
-
|
299
|
+
`:class_name` should be a string that has the name of a class
|
304
300
|
you've already included. If your class is inside of a module,
|
305
301
|
be sure to put the whole name, e.g.
|
306
|
-
|
302
|
+
`:class_name => "MyLdapModule::Group"`.
|
307
303
|
|
308
|
-
|
304
|
+
`:many` and `:primary_key` are similar. Both of them specifies attribute name of related object specified by `:foreign_key`. Those values are attribute name that can be used by object of class specified by `:class_name`.
|
309
305
|
|
310
|
-
Relation is resolved by searching entries of
|
306
|
+
Relation is resolved by searching entries of `:class_name` class with `:foreign_key` attribute value. Search target attribute for it is `:primary_key` or `:many`. primary_group method in the above example searches Group objects with User object's gidNumber value as Group object's gidNumber value. Matched Group objects are belonged objects.
|
311
307
|
|
312
|
-
|
308
|
+
`:parimary_key` is used for an object just belongs to an object. The first matched object is treated as beloned object.
|
313
309
|
|
314
|
-
|
310
|
+
`:many` is used for an object belongs to many objects. All of matched objects are treated as belonged objects.
|
315
311
|
|
316
|
-
|
312
|
+
##### `has_many`
|
317
313
|
|
318
314
|
This method is the opposite of belongs_to. Instead of checking other objects in
|
319
315
|
other parts of the LDAP tree to see if you belong to them, you have multiple
|
320
316
|
objects from other trees listed in your object. To show this, we can just
|
321
317
|
invert the example from above:
|
322
318
|
|
323
|
-
|
319
|
+
```ruby
|
324
320
|
class Group < ActiveLdap::Base
|
325
321
|
ldap_mapping :dn_attribute => 'cn', :prefix => 'ou=Groups', :classes => ['top', 'posixGroup']
|
326
322
|
|
@@ -332,104 +328,104 @@ class Group < ActiveLdap::Base
|
|
332
328
|
has_many :members, :wrap => "memberUid",
|
333
329
|
:class_name => "User", :primary_key => 'uid'
|
334
330
|
end
|
335
|
-
|
331
|
+
```
|
336
332
|
|
337
333
|
Now we can see that group develop has user 'drewry' as a member, and it can
|
338
|
-
even return all responses in object form just like belongs_to methods.
|
334
|
+
even return all responses in object form just like `belongs_to` methods.
|
339
335
|
|
340
|
-
|
336
|
+
```text
|
341
337
|
irb> develop = Group.find('develop')
|
342
338
|
=> ...
|
343
339
|
irb> develop.members
|
344
340
|
=> #<ActiveLdap::Association::HasManyWrap:..> # Enumerable object
|
345
341
|
irb> develop.members.map{|member| member.id}
|
346
342
|
=> ["drewry", "builder"]
|
347
|
-
|
343
|
+
```
|
348
344
|
|
349
|
-
The arguments for has_many follow the exact same idea that belongs_to's
|
345
|
+
The arguments for `has_many` follow the exact same idea that `belongs_to`'s
|
350
346
|
arguments followed. :wrap's contents are used to search for matching
|
351
|
-
|
352
|
-
dn_attribute of the specified
|
347
|
+
`:primary_key` content. If `:primary_key` is not specified, it defaults to the
|
348
|
+
dn_attribute of the specified `:class_name`.
|
353
349
|
|
354
|
-
|
350
|
+
### Using these new classes
|
355
351
|
|
356
352
|
These new classes have many method calls. Many of them are automatically
|
357
353
|
generated to provide access to the LDAP object's attributes. Other were defined
|
358
|
-
during class creation by special methods like belongs_to
|
354
|
+
during class creation by special methods like `belongs_to`. There are a few other
|
359
355
|
methods that do not fall in to these categories.
|
360
356
|
|
361
|
-
|
357
|
+
#### `.find`
|
362
358
|
|
363
|
-
|
359
|
+
`.find` is a class method that is accessible from
|
364
360
|
any subclass of Base that has 'ldap_mapping' called. When
|
365
|
-
called
|
361
|
+
called `.first(:first)` returns the first match of the given class.
|
366
362
|
|
367
|
-
|
363
|
+
```text
|
368
364
|
irb> Group.find(:first, 'deve*").cn
|
369
365
|
=> "develop"
|
370
|
-
|
366
|
+
```
|
371
367
|
|
372
368
|
In this simple example, Group.find took the search string of 'deve*' and
|
373
369
|
searched for the first match in Group where the dn_attribute matched the
|
374
370
|
query. This is the simplest example of .find.
|
375
371
|
|
376
|
-
|
372
|
+
```text
|
377
373
|
irb> Group.find(:all).collect {|group| group.cn}
|
378
374
|
=> ["root", "daemon", "bin", "sys", "adm", "tty", ..., "develop"]
|
379
|
-
|
375
|
+
```
|
380
376
|
|
381
377
|
Here .find(:all) returns all matches to the same query. Both .find(:first) and
|
382
378
|
.find(:all) also can take more expressive arguments:
|
383
379
|
|
384
|
-
|
380
|
+
```text
|
385
381
|
irb> Group.find(:all, :attribute => 'gidNumber', :value => '1003').collect {|group| group.cn}
|
386
382
|
=> ["develop"]
|
387
|
-
|
383
|
+
```
|
388
384
|
|
389
385
|
So it is pretty clear what :attribute and :value do - they are used to query as
|
390
|
-
|
386
|
+
`:attribute=:value`.
|
391
387
|
|
392
388
|
If :attribute is unspecified, it defaults to the dn_attribute.
|
393
389
|
|
394
390
|
It is also possible to override :attribute and :value by specifying :filter. This
|
395
391
|
argument allows the direct specification of a LDAP filter to retrieve objects by.
|
396
392
|
|
397
|
-
|
393
|
+
##### Using the :filter option
|
398
394
|
|
399
395
|
The filter option lets you pass in an LDAP query string.
|
400
396
|
For example retrieving all groups with cn which starts with @'dev'@ and has @guid@ == 1:
|
401
397
|
|
402
|
-
|
398
|
+
```text
|
403
399
|
irb> Group.find(:all, :filter => '(&(cn=dev*)(guid=1))').collect {|group| group.cn}
|
404
400
|
=> ["develop"]
|
405
|
-
|
401
|
+
```
|
406
402
|
|
407
403
|
It also allows a hash like sintax (sparing you the need to write the query by hand ):
|
408
404
|
|
409
|
-
|
405
|
+
```text
|
410
406
|
irb> Group.find(:all, :filter => {:cn => 'dev*', :guid => 1 }).collect {|group| group.cn}
|
411
407
|
=> ["develop", "developers", "sys", "sysadmin"]
|
412
|
-
|
408
|
+
```
|
413
409
|
|
414
410
|
You can build complex queries combining the hash syntax with arrays and @:or@ and @:and@ operators retrieving all users whose name contains 'john' or cn ends with 'smith' or contains 'liz'
|
415
411
|
|
416
|
-
|
412
|
+
```text
|
417
413
|
irb> User.find(:all, filter: [:or, [:or, { :cn => '*smith', :name => '*john*'} ], { cn: '*liz*' }]).collect(&:cn)
|
418
414
|
=> ['john.smith', 'jane.smith', 'john tha ripper', 'liz.taylor', ...]
|
419
|
-
|
415
|
+
```
|
420
416
|
|
421
|
-
|
417
|
+
#### .search
|
422
418
|
|
423
419
|
.search is a class method that is accessible from any subclass of Base, and Base.
|
424
420
|
It lets the user perform an arbitrary search against the current LDAP connection
|
425
421
|
irrespetive of LDAP mapping data. This is meant to be useful as a utility method
|
426
422
|
to cover 80% of the cases where a user would want to use Base.connection directly.
|
427
423
|
|
428
|
-
|
424
|
+
```text
|
429
425
|
irb> Base.search(:base => 'dc=example,dc=com', :filter => '(uid=roo*)',
|
430
426
|
:scope => :sub, :attributes => ['uid', 'cn'])
|
431
427
|
=> [["uid=root,ou=People,dc=dataspill,dc=org",{"cn"=>["root"], "uidNumber"=>["0"]}]
|
432
|
-
|
428
|
+
```
|
433
429
|
|
434
430
|
You can specify the :filter, :base, :scope, and :attributes, but they all have defaults --
|
435
431
|
* :filter defaults to objectClass=* - usually this isn't what you want
|
@@ -437,41 +433,41 @@ You can specify the :filter, :base, :scope, and :attributes, but they all have d
|
|
437
433
|
* :scope defaults to :sub. Usually you won't need to change it (You can choose value also from between :one and :base)
|
438
434
|
* :attributes defaults to [] and is the list of attributes you want back. Empty means all of them.
|
439
435
|
|
440
|
-
|
436
|
+
#### #valid?
|
441
437
|
|
442
438
|
valid? is a method that verifies that all attributes that are required by the
|
443
439
|
objects current objectClasses are populated.
|
444
440
|
|
445
|
-
|
441
|
+
#### #save
|
446
442
|
|
447
443
|
save is a method that writes any changes to an object back to the LDAP server.
|
448
444
|
It automatically handles the addition of new objects, and the modification of
|
449
445
|
existing ones.
|
450
446
|
|
451
|
-
|
447
|
+
#### .exists?
|
452
448
|
|
453
449
|
exists? is a simple method which returns true is the current object exists in
|
454
450
|
LDAP, or false if it does not.
|
455
451
|
|
456
|
-
|
452
|
+
```text
|
457
453
|
irb> User.exists?("dshadsadsa")
|
458
454
|
=> false
|
459
|
-
|
455
|
+
```
|
460
456
|
|
461
457
|
|
462
|
-
|
458
|
+
### ActiveLdap::Base
|
463
459
|
|
464
460
|
ActiveLdap::Base has come up a number of times in the examples above. Every
|
465
461
|
time, it was being used as the super class for the wrapper objects. While this
|
466
462
|
is it's main purpose, it also handles quite a bit more in the background.
|
467
463
|
|
468
|
-
|
464
|
+
#### What is it?
|
469
465
|
|
470
466
|
ActiveLdap::Base is the heart of ActiveLdap. It does all the schema
|
471
467
|
parsing for validation and attribute-to-method mangling as well as manage the
|
472
468
|
connection to LDAP.
|
473
469
|
|
474
|
-
|
470
|
+
##### setup_connection
|
475
471
|
|
476
472
|
Base.setup_connection takes many (optional) arguments and is used to
|
477
473
|
connect to the LDAP server. Sometimes you will want to connect anonymously
|
@@ -485,7 +481,7 @@ server allows anonymous binding, and you only want to access data in a
|
|
485
481
|
read-only fashion, you won't need to call Base.setup_connection. Here
|
486
482
|
is a fully parameterized call:
|
487
483
|
|
488
|
-
|
484
|
+
```ruby
|
489
485
|
Base.setup_connection(
|
490
486
|
:host => 'ldap.dataspill.org',
|
491
487
|
:port => 389,
|
@@ -496,7 +492,7 @@ Base.setup_connection(
|
|
496
492
|
:allow_anonymous => false,
|
497
493
|
:try_sasl => false
|
498
494
|
)
|
499
|
-
|
495
|
+
```
|
500
496
|
|
501
497
|
There are quite a few arguments, but luckily many of them have safe defaults:
|
502
498
|
* :host defaults to "127.0.0.1".
|
@@ -550,51 +546,51 @@ in an internal class variable which is used to cache the
|
|
550
546
|
information without ditching the defaults passed in from
|
551
547
|
configuration.rb
|
552
548
|
|
553
|
-
|
549
|
+
##### connection
|
554
550
|
|
555
551
|
Base.connection returns the ActiveLdap::Connection object.
|
556
552
|
|
557
|
-
|
553
|
+
### Exceptions
|
558
554
|
|
559
555
|
There are a few custom exceptions used in ActiveLdap. They are detailed below.
|
560
556
|
|
561
|
-
|
557
|
+
#### DeleteError
|
562
558
|
|
563
559
|
This exception is raised when #delete fails. It will include LDAP error
|
564
560
|
information that was passed up during the error.
|
565
561
|
|
566
|
-
|
562
|
+
#### SaveError
|
567
563
|
|
568
564
|
This exception is raised when there is a problem in #save updating or creating
|
569
565
|
an LDAP entry. Often the error messages are cryptic. Looking at the server
|
570
566
|
logs or doing an "Wireshark":http://www.wireshark.org dump of the connection will
|
571
567
|
often provide better insight.
|
572
568
|
|
573
|
-
|
569
|
+
#### AuthenticationError
|
574
570
|
|
575
571
|
This exception is raised during Base.setup_connection if no valid authentication methods
|
576
572
|
succeeded.
|
577
573
|
|
578
|
-
|
574
|
+
#### ConnectionError
|
579
575
|
|
580
576
|
This exception is raised during Base.setup_connection if no valid
|
581
577
|
connection to the LDAP server could be created. Check you
|
582
578
|
Base.setup_connection arguments, and network connectivity! Also check
|
583
579
|
your LDAP server logs to see if it ever saw the request.
|
584
580
|
|
585
|
-
|
581
|
+
#### ObjectClassError
|
586
582
|
|
587
583
|
This exception is raised when an object class is used that is not defined
|
588
584
|
in the schema.
|
589
585
|
|
590
|
-
|
586
|
+
### Others
|
591
587
|
|
592
588
|
Other exceptions may be raised by the Ruby/LDAP module, or by other subsystems.
|
593
589
|
If you get one of these exceptions and think it should be wrapped, write me an
|
594
590
|
email and let me know where it is and what you expected. For faster results,
|
595
591
|
email a patch!
|
596
592
|
|
597
|
-
|
593
|
+
### Putting it all together
|
598
594
|
|
599
595
|
Now that all of the components of ActiveLdap have been covered, it's time
|
600
596
|
to put it all together! The rest of this section will show the steps to setup
|
@@ -603,50 +599,48 @@ above.
|
|
603
599
|
|
604
600
|
All of the scripts here are in the package's examples/ directory.
|
605
601
|
|
606
|
-
|
602
|
+
#### Setting up
|
607
603
|
|
608
604
|
Create directory for scripts.
|
609
605
|
|
610
|
-
|
611
|
-
!!!plain
|
606
|
+
```console
|
612
607
|
% mkdir -p ldapadmin/objects
|
613
|
-
|
608
|
+
```
|
614
609
|
|
615
610
|
In ldapadmin/objects/ create the file user.rb:
|
616
611
|
|
617
|
-
|
612
|
+
```ruby
|
618
613
|
require 'objects/group'
|
619
614
|
|
620
615
|
class User < ActiveLdap::Base
|
621
616
|
ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People', :classes => ['person', 'posixAccount']
|
622
617
|
belongs_to :groups, :class_name => 'Group', :many => 'memberUid'
|
623
618
|
end
|
624
|
-
|
619
|
+
```
|
625
620
|
|
626
621
|
In ldapadmin/objects/ create the file group.rb:
|
627
622
|
|
628
|
-
|
623
|
+
```ruby
|
629
624
|
class Group < ActiveLdap::Base
|
630
625
|
ldap_mapping :classes => ['top', 'posixGroup'], :prefix => 'ou=Groups'
|
631
626
|
has_many :members, :class_name => "User", :wrap => "memberUid"
|
632
627
|
has_many :primary_members, :class_name => 'User', :foreign_key => 'gidNumber', :primary_key => 'gidNumber'
|
633
628
|
end
|
634
|
-
|
629
|
+
```
|
635
630
|
|
636
631
|
Now, we can write some small scripts to do simple management tasks.
|
637
632
|
|
638
|
-
|
633
|
+
#### Creating LDAP entries
|
639
634
|
|
640
635
|
Now let's create a really dumb script for adding users - ldapadmin/useradd:
|
641
636
|
|
642
|
-
|
637
|
+
```ruby
|
643
638
|
#!/usr/bin/ruby -W0
|
644
639
|
|
645
640
|
base = File.expand_path(File.join(File.dirname(__FILE__), ".."))
|
646
641
|
$LOAD_PATH << File.join(base, "lib")
|
647
642
|
$LOAD_PATH << File.join(base, "examples")
|
648
643
|
|
649
|
-
require 'rubygems'
|
650
644
|
require 'active_ldap'
|
651
645
|
require 'objects/user'
|
652
646
|
require 'objects/group'
|
@@ -686,20 +680,19 @@ unless user.save
|
|
686
680
|
puts user.errors.full_messages
|
687
681
|
exit 1
|
688
682
|
end
|
689
|
-
|
683
|
+
```
|
690
684
|
|
691
|
-
|
685
|
+
#### Managing LDAP entries
|
692
686
|
|
693
687
|
Now let's create another dumb script for modifying users - ldapadmin/usermod:
|
694
688
|
|
695
|
-
|
689
|
+
```ruby
|
696
690
|
#!/usr/bin/ruby -W0
|
697
691
|
|
698
692
|
base = File.expand_path(File.join(File.dirname(__FILE__), ".."))
|
699
693
|
$LOAD_PATH << File.join(base, "lib")
|
700
694
|
$LOAD_PATH << File.join(base, "examples")
|
701
695
|
|
702
|
-
require 'rubygems'
|
703
696
|
require 'active_ldap'
|
704
697
|
require 'objects/user'
|
705
698
|
require 'objects/group'
|
@@ -736,20 +729,19 @@ unless user.save
|
|
736
729
|
puts user.errors.full_messages
|
737
730
|
exit 1
|
738
731
|
end
|
739
|
-
|
732
|
+
```
|
740
733
|
|
741
|
-
|
734
|
+
#### Removing LDAP entries
|
742
735
|
|
743
736
|
Now let's create more one for deleting users - ldapadmin/userdel:
|
744
737
|
|
745
|
-
|
738
|
+
```ruby
|
746
739
|
#!/usr/bin/ruby -W0
|
747
740
|
|
748
741
|
base = File.expand_path(File.join(File.dirname(__FILE__), ".."))
|
749
742
|
$LOAD_PATH << File.join(base, "lib")
|
750
743
|
$LOAD_PATH << File.join(base, "examples")
|
751
744
|
|
752
|
-
require 'rubygems'
|
753
745
|
require 'active_ldap'
|
754
746
|
require 'objects/user'
|
755
747
|
require 'objects/group'
|
@@ -778,20 +770,20 @@ unless User.exists?(name)
|
|
778
770
|
end
|
779
771
|
|
780
772
|
User.destroy(name)
|
781
|
-
|
773
|
+
```
|
782
774
|
|
783
|
-
|
775
|
+
### Advanced Topics
|
784
776
|
|
785
777
|
Below are some situation tips and tricks to get the most out of ActiveLdap.
|
786
778
|
|
787
779
|
|
788
|
-
|
780
|
+
#### Binary data and other subtypes
|
789
781
|
|
790
782
|
Sometimes, you may want to store attributes with language specifiers, or
|
791
783
|
perhaps in binary form. This is (finally!) fully supported. To do so,
|
792
784
|
follow the examples below:
|
793
785
|
|
794
|
-
|
786
|
+
```text
|
795
787
|
irb> user = User.new('drewry')
|
796
788
|
=> ...
|
797
789
|
# This adds a cn entry in lang-en and whatever the server default is.
|
@@ -803,7 +795,7 @@ irb> user.cn
|
|
803
795
|
irb> user.user_certificate = File.read('example.der')
|
804
796
|
=> ...
|
805
797
|
irb> user.save
|
806
|
-
|
798
|
+
```
|
807
799
|
|
808
800
|
So that's a lot to take in. Here's what is going on. I just set the LDAP
|
809
801
|
object's cn to "wad" and cn:lang-en-us to ["wad", "Will Drewry"].
|
@@ -815,9 +807,9 @@ get wrapped in @{'binary' => value}@ if you don't do it. This keeps your #writes
|
|
815
807
|
from breaking, and my code from crying. For correctness, I could have easily
|
816
808
|
done the following:
|
817
809
|
|
818
|
-
|
810
|
+
```text
|
819
811
|
irb> user.user_certificate = {'binary' => File.read('example.der')}
|
820
|
-
|
812
|
+
```
|
821
813
|
|
822
814
|
You should note that some binary data does not use the binary subtype all the time.
|
823
815
|
One example is jpegPhoto. You can use it as jpegPhoto;binary or just as jpegPhoto.
|
@@ -829,9 +821,9 @@ LDAP site policy and not by any programmatic means.
|
|
829
821
|
The only subtypes defined in LDAPv3 are lang-* and binary. These can be nested
|
830
822
|
though:
|
831
823
|
|
832
|
-
|
824
|
+
```text
|
833
825
|
irb> user.cn = [{'lang-ja' => {'binary' => 'some Japanese'}}]
|
834
|
-
|
826
|
+
```
|
835
827
|
|
836
828
|
As I understand it, OpenLDAP does not support nested subtypes, but some
|
837
829
|
documentation I've read suggests that Netscape's LDAP server does. I only
|
@@ -841,7 +833,7 @@ goes!
|
|
841
833
|
|
842
834
|
And that pretty much wraps up this section.
|
843
835
|
|
844
|
-
|
836
|
+
#### Further integration with your environment aka namespacing
|
845
837
|
|
846
838
|
If you want this to cleanly integrate into your system-wide Ruby include path,
|
847
839
|
you should put your extension classes inside a custom module.
|
@@ -851,28 +843,28 @@ Example:
|
|
851
843
|
|
852
844
|
./myldap.rb:
|
853
845
|
|
854
|
-
|
846
|
+
```ruby
|
855
847
|
require 'active_ldap'
|
856
848
|
require 'myldap/user'
|
857
849
|
require 'myldap/group'
|
858
850
|
module MyLDAP
|
859
851
|
end
|
860
|
-
|
852
|
+
```
|
861
853
|
|
862
854
|
./myldap/user.rb:
|
863
855
|
|
864
|
-
|
856
|
+
```ruby
|
865
857
|
module MyLDAP
|
866
858
|
class User < ActiveLdap::Base
|
867
859
|
ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People', :classes => ['top', 'account', 'posixAccount']
|
868
860
|
belongs_to :groups, :class_name => 'MyLDAP::Group', :many => 'memberUid'
|
869
861
|
end
|
870
862
|
end
|
871
|
-
|
863
|
+
```
|
872
864
|
|
873
865
|
./myldap/group.rb:
|
874
866
|
|
875
|
-
|
867
|
+
```ruby
|
876
868
|
module MyLDAP
|
877
869
|
class Group < ActiveLdap::Base
|
878
870
|
ldap_mapping :classes => ['top', 'posixGroup'], :prefix => 'ou=Groups'
|
@@ -880,54 +872,54 @@ module MyLDAP
|
|
880
872
|
has_many :primary_members, :class_name => 'MyLDAP::User', :foreign_key => 'gidNumber', :primary_key => 'gidNumber'
|
881
873
|
end
|
882
874
|
end
|
883
|
-
|
875
|
+
```
|
884
876
|
|
885
877
|
Now in your local applications, you can call
|
886
878
|
|
887
|
-
|
879
|
+
```ruby
|
888
880
|
require 'myldap'
|
889
881
|
|
890
882
|
MyLDAP::Group.new('foo')
|
891
883
|
...
|
892
|
-
|
884
|
+
```
|
893
885
|
|
894
886
|
and everything should work well.
|
895
887
|
|
896
888
|
|
897
|
-
|
889
|
+
#### force array results for single values
|
898
890
|
|
899
891
|
Even though ActiveLdap attempts to maintain programmatic ease by
|
900
892
|
returning Array values only. By specifying 'true' as an argument to
|
901
893
|
any attribute method you will get back a Array if it is single value.
|
902
894
|
Here's an example:
|
903
895
|
|
904
|
-
|
896
|
+
```text
|
905
897
|
irb> user = User.new('drewry')
|
906
898
|
=> ...
|
907
899
|
irb> user.cn(true)
|
908
900
|
=> ["Will Drewry"]
|
909
|
-
|
901
|
+
```
|
910
902
|
|
911
|
-
|
903
|
+
#### Dynamic attribute crawling
|
912
904
|
|
913
905
|
If you use tab completion in irb, you'll notice that you /can/ tab complete the dynamic
|
914
906
|
attribute methods. You can still see which methods are for attributes using
|
915
907
|
Base#attribute_names:
|
916
908
|
|
917
|
-
|
909
|
+
```text
|
918
910
|
irb> d = Group.new('develop')
|
919
911
|
=> ...
|
920
912
|
irb> d.attribute_names
|
921
913
|
=> ["gidNumber", "cn", "memberUid", "commonName", "description", "userPassword", "objectClass"]
|
922
|
-
|
914
|
+
```
|
923
915
|
|
924
916
|
|
925
|
-
|
917
|
+
#### Juggling multiple LDAP connections
|
926
918
|
|
927
919
|
In the same vein as the last tip, you can use multiple LDAP connections by
|
928
920
|
per class as follows:
|
929
921
|
|
930
|
-
|
922
|
+
```text
|
931
923
|
irb> anon_class = Class.new(Base)
|
932
924
|
=> ...
|
933
925
|
irb> anon_class.setup_connection
|
@@ -936,11 +928,11 @@ irb> auth_class = Class.new(Base)
|
|
936
928
|
=> ...
|
937
929
|
irb> auth_class.setup_connection(:password_block => lambda{'mypass'})
|
938
930
|
=> ...
|
939
|
-
|
931
|
+
```
|
940
932
|
|
941
933
|
This can be useful for doing authentication tests and other such tricks.
|
942
934
|
|
943
|
-
|
935
|
+
#### :try_sasl
|
944
936
|
|
945
937
|
If you have the Ruby/LDAP package with the SASL/GSSAPI patch from Ian
|
946
938
|
MacDonald's web site, you can use Kerberos to bind to your LDAP server. By
|
@@ -949,7 +941,7 @@ default, :try_sasl is false.
|
|
949
941
|
Also note that you must be using OpenLDAP 2.1.29 or higher to use SASL/GSSAPI
|
950
942
|
due to some bugs in older versions of OpenLDAP.
|
951
943
|
|
952
|
-
|
944
|
+
#### Don't be afraid! [Internals]
|
953
945
|
|
954
946
|
Don't be afraid to add more methods to the extensions classes and to
|
955
947
|
experiment. That's exactly how I ended up with this package. If you come up
|
@@ -959,7 +951,7 @@ The internal structure of ActiveLdap::Base, and thus all its subclasses, is
|
|
959
951
|
still in flux. I've tried to minimize the changes to the overall API, but
|
960
952
|
the internals are still rough around the edges.
|
961
953
|
|
962
|
-
|
954
|
+
##### Where's ldap_mapping data stored? How can I get to it?
|
963
955
|
|
964
956
|
When you call ldap_mapping, it overwrites several class methods inherited
|
965
957
|
from Base:
|
@@ -974,7 +966,7 @@ from any new instance methods you define:
|
|
974
966
|
* Base#required_classes()
|
975
967
|
* Base#dn_attribute()
|
976
968
|
|
977
|
-
|
969
|
+
##### What else?
|
978
970
|
|
979
971
|
Well if you want to use the LDAP connection for anything, I'd suggest still
|
980
972
|
calling Base.connection to get it. There really aren't many other internals
|
@@ -987,12 +979,12 @@ any methods you write might need to figure it out. I'd suggest just
|
|
987
979
|
calling self[attribname] to get the value, but if that's not good enough,
|
988
980
|
you can call look up the stored name by #to_real_attribute_name as follows:
|
989
981
|
|
990
|
-
|
982
|
+
```text
|
991
983
|
irb> User.find(:first).instance_eval do
|
992
984
|
irb> to_real_attribute_name('commonName')
|
993
985
|
irb> end
|
994
986
|
=> 'cn'
|
995
|
-
|
987
|
+
```
|
996
988
|
|
997
989
|
This tells you the name the attribute is stored in behind the scenes (@data).
|
998
990
|
Again, self[attribname] should be enough for most extensions, but if not,
|
@@ -1001,16 +993,16 @@ it's probably safe to dabble here.
|
|
1001
993
|
Also, if you like to look up all aliases for an attribute, you can call the
|
1002
994
|
following:
|
1003
995
|
|
1004
|
-
|
996
|
+
```text
|
1005
997
|
irb> User.schema.attribute_type 'cn', 'NAME'
|
1006
998
|
=> ["cn", "commonName"]
|
1007
|
-
|
999
|
+
```
|
1008
1000
|
|
1009
1001
|
This is discovered automagically from the LDAP server's schema.
|
1010
1002
|
|
1011
|
-
|
1003
|
+
## Limitations
|
1012
1004
|
|
1013
|
-
|
1005
|
+
### Speed
|
1014
1006
|
|
1015
1007
|
Currently, ActiveLdap could be faster. I have some recursive type
|
1016
1008
|
checking going on which slows object creation down, and I'm sure there
|
@@ -1018,7 +1010,7 @@ are many, many other places optimizations can be done. Feel free
|
|
1018
1010
|
to send patches, or just hang in there until I can optimize away the
|
1019
1011
|
slowness.
|
1020
1012
|
|
1021
|
-
|
1013
|
+
## Feedback
|
1022
1014
|
|
1023
1015
|
Any and all feedback and patches are welcome. I am very excited about this
|
1024
1016
|
package, and I'd like to see it prove helpful to more people than just myself.
|