activeldap 5.2.4 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +3 -1
- data/doc/text/development.md +26 -0
- data/doc/text/{news.textile → news.md} +367 -271
- 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 +30 -10
- 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 +13 -22
- data/lib/active_ldap/adapter/ldap_ext.rb +32 -13
- data/lib/active_ldap/adapter/net_ldap.rb +9 -18
- data/lib/active_ldap/base.rb +11 -1
- data/lib/active_ldap/configuration.rb +24 -1
- data/lib/active_ldap/connection.rb +2 -2
- data/lib/active_ldap/human_readable.rb +5 -4
- data/lib/active_ldap/operations.rb +21 -4
- data/lib/active_ldap/persistence.rb +3 -2
- data/lib/active_ldap/validations.rb +12 -4
- data/lib/active_ldap/version.rb +1 -1
- data/lib/active_ldap.rb +0 -1
- data/test/add-phonetic-attribute-options-to-slapd.ldif +1 -1
- data/test/al-test-utils.rb +125 -38
- data/test/enable-dynamic-groups.ldif +22 -0
- data/test/enable-start-tls.ldif +1 -1
- data/test/run-test.rb +0 -4
- data/test/test_base.rb +76 -18
- data/test/test_base_per_instance.rb +33 -1
- data/test/test_connection.rb +4 -0
- data/test/test_entry.rb +1 -0
- data/test/test_find.rb +12 -2
- data/test/test_supported_control.rb +1 -1
- data/test/test_validation.rb +28 -15
- metadata +16 -30
- data/README.textile +0 -141
- data/doc/text/development.textile +0 -54
@@ -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.
|