activeldap 1.2.4 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/Gemfile +18 -0
  2. data/LICENSE +2 -1
  3. data/README.textile +137 -0
  4. data/doc/text/development.textile +50 -0
  5. data/{CHANGES → doc/text/news.textile} +256 -237
  6. data/doc/text/rails.textile +144 -0
  7. data/doc/text/tutorial.textile +1005 -0
  8. data/lib/active_ldap/adapter/base.rb +5 -3
  9. data/lib/active_ldap/adapter/net_ldap_ext.rb +1 -1
  10. data/lib/active_ldap/associations.rb +6 -2
  11. data/lib/active_ldap/base.rb +16 -71
  12. data/lib/active_ldap/callbacks.rb +52 -33
  13. data/lib/active_ldap/configuration.rb +2 -2
  14. data/lib/active_ldap/get_text/parser.rb +2 -2
  15. data/lib/active_ldap/human_readable.rb +5 -4
  16. data/lib/active_ldap/log_subscriber.rb +50 -0
  17. data/lib/active_ldap/persistence.rb +65 -0
  18. data/lib/active_ldap/railtie.rb +40 -0
  19. data/lib/active_ldap/railties/controller_runtime.rb +48 -0
  20. data/lib/active_ldap/user_password.rb +1 -0
  21. data/lib/active_ldap/validations.rb +34 -72
  22. data/lib/active_ldap.rb +13 -912
  23. data/{rails_generators/model_active_ldap → lib/rails/generators/active_ldap/model}/USAGE +2 -1
  24. data/lib/rails/generators/active_ldap/model/model_generator.rb +47 -0
  25. data/{rails_generators/model_active_ldap → lib/rails/generators/active_ldap/model}/templates/model_active_ldap.rb +0 -0
  26. data/lib/rails/generators/active_ldap/scaffold/scaffold_generator.rb +14 -0
  27. data/{rails_generators/scaffold_active_ldap → lib/rails/generators/active_ldap/scaffold}/templates/ldap.yml +1 -0
  28. data/test/test_base.rb +9 -0
  29. data/test/test_callback.rb +2 -6
  30. data/test/test_connection.rb +2 -2
  31. data/test/test_user.rb +2 -2
  32. data/test/test_validation.rb +11 -11
  33. metadata +165 -106
  34. data/README +0 -155
  35. data/Rakefile +0 -133
  36. data/rails/README +0 -54
  37. data/rails/init.rb +0 -33
  38. data/rails_generators/model_active_ldap/model_active_ldap_generator.rb +0 -69
  39. data/rails_generators/model_active_ldap/templates/unit_test.rb +0 -8
  40. data/rails_generators/scaffold_active_ldap/scaffold_active_ldap_generator.rb +0 -7
  41. data/test/al-test-utils.rb +0 -439
  42. data/test/command.rb +0 -112
  43. data/test/config.yaml.sample +0 -6
  44. data/test/fixtures/lower_case_object_class_schema.rb +0 -802
  45. data/test/run-test.rb +0 -44
@@ -0,0 +1,1005 @@
1
+ h1. Tutorial
2
+
3
+ h2. Introduction
4
+
5
+ ActiveLdap is a novel way of interacting with LDAP. Most interaction with
6
+ LDAP is done using clunky LDIFs, web interfaces, or with painful APIs that
7
+ required a thick reference manual nearby. ActiveLdap aims to fix that.
8
+ Inspired by "ActiveRecord":http://activerecord.rubyonrails.org, ActiveLdap provides an
9
+ object oriented interface to LDAP entries.
10
+
11
+ The target audience is system administrators and LDAP users everywhere that
12
+ need quick, clean access to LDAP in Ruby.
13
+
14
+ h3. What's LDAP?
15
+
16
+ LDAP stands for "Lightweight Directory Access Protocol." Basically this means
17
+ that it is the protocol used for accessing LDAP servers. LDAP servers
18
+ lightweight directories. An LDAP server can contain anything from a simple
19
+ digital phonebook to user accounts for computer systems. More and more
20
+ frequently, it is being used for the latter. My examples in this text will
21
+ assume some familiarity with using LDAP as a centralized authentication and
22
+ authorization server for Unix systems. (Unfortunately, I've yet to try this
23
+ against Microsoft's ActiveDirectory, despite what the name implies.)
24
+
25
+ Further reading:
26
+ * "RFC1777":http://www.faqs.org/rfcs/rfc1777.html - Lightweight Directory Access Protocol
27
+ * "OpenLDAP":http://www.openldap.org
28
+
29
+ h3. So why use ActiveLdap?
30
+
31
+ Using LDAP directly (even with the excellent Ruby/LDAP), leaves you bound to
32
+ the world of the predefined LDAP API. While this API is important for many
33
+ reasons, having to extract code out of LDAP search blocks and create huge
34
+ arrays of LDAP.mod entries make code harder to read, less intuitive, and just
35
+ less fun to write. Hopefully, ActiveLdap will remedy all of these
36
+ problems!
37
+
38
+ h2. Getting Started
39
+
40
+ h3. Requirements
41
+
42
+ * A Ruby implementation: "Ruby":http://www.ruby-lang.org 1.8.x, 1.9.1 or "JRuby":http://jruby.codehaus.org/
43
+ * A LDAP library: "Ruby/LDAP":http://code.google.com/p/ruby-activeldap/wiki/RubyLDAP (for Ruby), "Net::LDAP":http://rubyforge.org/projects/net-ldap/ (for Ruby or JRuby) or JNDI (for JRuby)
44
+ * A LDAP server: "OpenLDAP":http://www.openldap.org, etc
45
+ ** Your LDAP server must allow root_dse queries to allow for schema queries
46
+
47
+ h3. Installation
48
+
49
+ Assuming all the requirements are installed, you can install by gem.
50
+
51
+ <pre>
52
+ # gem install activeldap
53
+ </pre>
54
+
55
+ Now as a quick test, you can run:
56
+
57
+ <pre>
58
+ $ irb -rubygems
59
+ irb> require 'active_ldap'
60
+ => true
61
+ irb> exit
62
+ </pre>
63
+
64
+ If the require returns false or an exception is raised, there has been a
65
+ problem with the installation. You may need to customize what setup.rb does on
66
+ install.
67
+
68
+ h2. Usage
69
+
70
+ This section covers using ActiveLdap from writing extension classes to
71
+ writing applications that use them.
72
+
73
+ Just to give a taste of what's to come, here is a quick example using irb:
74
+
75
+ <pre>
76
+ irb> require 'active_ldap'
77
+ </pre>
78
+
79
+ Call setup_connection method for connect to LDAP server. In this case, LDAP server
80
+ is localhost, and base of LDAP tree is "dc=dataspill,dc=org".
81
+
82
+ <pre>
83
+ irb> ActiveLdap::Base.setup_connection :host => 'localhost', :base => 'dc=dataspill,dc=org'
84
+ </pre>
85
+
86
+ Here's an extension class that maps to the LDAP Group objects:
87
+
88
+ <pre>
89
+ irb> class Group < ActiveLdap::Base
90
+ irb* ldap_mapping
91
+ irb* end
92
+ </pre>
93
+
94
+ In the above code, Group class handles sub tree of ou=Groups
95
+ tha is :base value specified by setup_connection. A instance
96
+ of Group class represents a LDAP object under ou=Gruops.
97
+
98
+ Here is the Group class in use:
99
+
100
+ <pre>
101
+ # Get all group names
102
+ irb> all_groups = Group.find(:all, '*').collect {|group| group.cn}
103
+ => ["root", "daemon", "bin", "sys", "adm", "tty", ..., "develop"]
104
+
105
+ # Get LDAP objects in develop group
106
+ irb> group = Group.find("develop")
107
+ => #<Group objectClass:<...> ...>
108
+
109
+ # Get cn of the develop group
110
+ irb> group.cn
111
+ => "develop"
112
+
113
+ # Get gid_number of the develop group
114
+ irb> group.gid_number
115
+ => "1003"
116
+ </pre>
117
+
118
+ That's it! No let's get back in to it.
119
+
120
+ h3. Extension Classes
121
+
122
+ Extension classes are classes that are subclassed from ActiveLdap::Base. They
123
+ are used to represent objects in your LDAP server abstractly.
124
+
125
+ h4. Why do I need them?
126
+
127
+ Extension classes are what make ActiveLdap "active"! They do all the
128
+ background work to make easy-to-use objects by mapping the LDAP object's
129
+ attributes on to a Ruby class.
130
+
131
+
132
+ h4. Special Methods
133
+
134
+ I will briefly talk about each of the methods you can use when defining an
135
+ extension class. In the above example, I only made one special method call
136
+ inside the Group class. More than likely, you will want to more than that.
137
+
138
+ h5. ldap_mapping
139
+
140
+ ldap_mapping is the only required method to setup an extension class for use
141
+ with ActiveLdap. It must be called inside of a subclass as shown above.
142
+
143
+ Below is a much more realistic Group class:
144
+
145
+ <pre>
146
+ class Group < ActiveLdap::Base
147
+ ldap_mapping :dn_attribute => 'cn',
148
+ :prefix => 'ou=Groups', :classes => ['top', 'posixGroup'],
149
+ :scope => :one
150
+ end
151
+ </pre>
152
+
153
+ As you can see, this method is used for defining how this class maps in to LDAP. Let's say that
154
+ my LDAP tree looks something like this:
155
+
156
+ <pre>
157
+ * dc=dataspill,dc=org
158
+ |- ou=People,dc=dataspill,dc=org
159
+ |+ ou=Groups,dc=dataspill,dc=org
160
+ \
161
+ |- cn=develop,ou=Groups,dc=dataspill,dc=org
162
+ |- cn=root,ou=Groups,dc=dataspill,dc=org
163
+ |- ...
164
+ </pre>
165
+
166
+ Under ou=People I store user objects, and under ou=Groups, I store group
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'
170
+ as the beginning of the distinguished name.
171
+
172
+ Just for clarity, here's how the arguments map out:
173
+
174
+ <pre>
175
+ cn=develop,ou=Groups,dc=dataspill,dc=org
176
+ ^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
177
+ :dn_attribute | |
178
+ :prefix |
179
+ :base from setup_connection
180
+ </pre>
181
+
182
+ :scope tells ActiveLdap to only search under ou=Groups, and not to look deeper
183
+ for dn_attribute matches.
184
+ (e.g. cn=develop,ou=DevGroups,ou=Groups,dc=dataspill,dc=org)
185
+ You can choose value from between :sub, :one and :base.
186
+
187
+ Something's missing: :classes. :classes is used to tell ActiveLdap what
188
+ the minimum requirement is when creating a new object. LDAP uses objectClasses
189
+ to define what attributes a LDAP object may have. ActiveLdap needs to know
190
+ what classes are required when creating a new object. Of course, you can leave
191
+ that field out to default to ['top'] only. Then you can let each application
192
+ choose what objectClasses their objects should have by calling the method e.g.
193
+ Group#add_class(*values).
194
+
195
+ Note that is can be very important to define the default :classes value. Due to
196
+ implementation choices with most LDAP servers, once an object is created, its
197
+ structural objectclasses may not be removed (or replaced). Setting a sane default
198
+ may help avoid programmer error later.
199
+
200
+ :classes isn't the only optional argument. If :dn_attribute is left off,
201
+ it defaults to super class's value or 'cn'. If :prefix is left off,
202
+ it will default to 'ou=PluralizedClassName'. In this
203
+ case, it would be 'ou=Groups'.
204
+
205
+ :classes should be an Array. :dn_attribute should be a String and so should
206
+ :prefix.
207
+
208
+
209
+ h5. belongs_to
210
+
211
+ This method allows an extension class to make use of other extension classes
212
+ tying objects together across the LDAP tree. Often, user objects will be
213
+ members of, or belong_to, Group objects.
214
+
215
+ <pre>
216
+ * dc=dataspill,dc=org
217
+ |+ ou=People,dc=dataspill,dc=org
218
+ \
219
+ |- uid=drewry,ou=People,dc=dataspill,dc=org
220
+ |- ou=Groups,dc=dataspill,dc=org
221
+ </pre>
222
+
223
+
224
+ In the above tree, one such example would be user 'drewry' who is a part of the
225
+ group 'develop'. You can see this by looking at the 'memberUid' field of 'develop'.
226
+
227
+ <pre>
228
+ irb> develop = Group.find('develop')
229
+ => ...
230
+ irb> develop.memberUid
231
+ => ['drewry', 'builder']
232
+ </pre>
233
+
234
+ If we look at the LDAP entry for 'drewry', we do not see any references to
235
+ group 'develop'. In order to remedy that, we can use belongs_to
236
+
237
+ <pre>
238
+ irb> class User < ActiveLdap::Base
239
+ irb* ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People', :classes => ['top','account']
240
+ irb* belongs_to :groups, :class_name => 'Group', :many => 'memberUid', :foreign_key => 'uid'
241
+ irb* end
242
+ </pre>
243
+
244
+ Now, class User will have a method called 'groups' which will retrieve all
245
+ Group objects that a user is in.
246
+
247
+ <pre>
248
+ irb> me = User.find('drewry')
249
+ irb> me.groups
250
+ => #<ActiveLdap::Association::BelongsToMany...> # Enumerable object
251
+ irb> me.groups.each { |group| p group.cn };nil
252
+ "cdrom"
253
+ "audio"
254
+ "develop"
255
+ => nil
256
+ (Note: nil is just there to make the output cleaner...)
257
+ </pre>
258
+
259
+ TIP: If you weren't sure what the distinguished name attribute was for Group,
260
+ you could also do the following:
261
+
262
+ <pre>
263
+ irb> me.groups.each { |group| p group.id };nil
264
+ "cdrom"
265
+ "audio"
266
+ "develop"
267
+ => nil
268
+ </pre>
269
+
270
+ Now let's talk about the arguments of belongs_to. We use the following code that extends Group group a bit for explain:
271
+
272
+ <pre>
273
+ class User < ActiveLdap::Base
274
+ ldap_mapping :dn_attribute => 'uid', :prefix => 'People', :classes => ['top','account']
275
+
276
+ # Associate with primary belonged group
277
+ belongs_to :primary_group, :foreign_key => 'gidNumber',
278
+ :class_name => 'Group', :primary_key => 'gidNumber'
279
+
280
+ # Associate with all belonged groups
281
+ belongs_to :groups, :foreign_key => 'uid',
282
+ :class_name => 'Group', :many => 'memberUid',
283
+ end
284
+ </pre>
285
+
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).
287
+
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.
289
+
290
+ In the example, uid is used for :foreign_key. It may confuse you.
291
+
292
+ ActiveLdap uses :foreign_key as "own attribute name". So it
293
+ may not be "foreign key". You can consider :foreign_key just
294
+ as a relation key.
295
+
296
+ :primary_key is treated as "related object's attribute name"
297
+ as we discussed later.
298
+
299
+ :class_name should be a string that has the name of a class
300
+ you've already included. If your class is inside of a module,
301
+ be sure to put the whole name, e.g.
302
+ @:class_name => "MyLdapModule::Group"@.
303
+
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.
305
+
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.
307
+
308
+ :parimary_key is used for an object just belongs to an object. The first matched object is treated as beloned object.
309
+
310
+ :many is used for an object belongs to many objects. All of matched objects are treated as belonged objects.
311
+
312
+ In addition, you can do simple membership tests by doing the following:
313
+
314
+ <pre>
315
+ irb> me.groups.member? 'root'
316
+ => false
317
+ irb> me.groups.member? 'develop'
318
+ => true
319
+ </pre>
320
+
321
+ h5. has_many
322
+
323
+ This method is the opposite of belongs_to. Instead of checking other objects in
324
+ other parts of the LDAP tree to see if you belong to them, you have multiple
325
+ objects from other trees listed in your object. To show this, we can just
326
+ invert the example from above:
327
+
328
+ <pre>
329
+ class Group < ActiveLdap::Base
330
+ ldap_mapping :dn_attribute => 'cn', :prefix => 'ou=Groups', :classes => ['top', 'posixGroup']
331
+
332
+ # Associate with primary belonged users
333
+ has_many :primary_members, :foreign_key => 'gidNumber',
334
+ :class_name => "User", :primary_key => 'gidNumber'
335
+
336
+ # Associate with all belonged users
337
+ has_many :members, :wrap => "memberUid",
338
+ :class_name => "User", :primary_key => 'uid'
339
+ end
340
+ </pre>
341
+
342
+ Now we can see that group develop has user 'drewry' as a member, and it can
343
+ even return all responses in object form just like belongs_to methods.
344
+
345
+ <pre>
346
+ irb> develop = Group.find('develop')
347
+ => ...
348
+ irb> develop.members
349
+ => #<ActiveLdap::Association::HasManyWrap:..> # Enumerable object
350
+ irb> develop.members.map{|member| member.id}
351
+ => ["drewry", "builder"]
352
+ </pre>
353
+
354
+ The arguments for has_many follow the exact same idea that belongs_to's
355
+ arguments followed. :wrap's contents are used to search for matching
356
+ :primary_key content. If :primary_key is not specified, it defaults to the
357
+ dn_attribute of the specified :class_name.
358
+
359
+ h3. Using these new classes
360
+
361
+ These new classes have many method calls. Many of them are automatically
362
+ generated to provide access to the LDAP object's attributes. Other were defined
363
+ during class creation by special methods like belongs_to. There are a few other
364
+ methods that do not fall in to these categories.
365
+
366
+ h4. .find
367
+
368
+ .find is a class method that is accessible from
369
+ any subclass of Base that has 'ldap_mapping' called. When
370
+ called .first(:first) returns the first match of the given class.
371
+
372
+ <pre>
373
+ irb> Group.find(:first, 'deve*").cn
374
+ => "develop"
375
+ </pre>
376
+
377
+ In this simple example, Group.find took the search string of 'deve*' and
378
+ searched for the first match in Group where the dn_attribute matched the
379
+ query. This is the simplest example of .find.
380
+
381
+ <pre>
382
+ irb> Group.find(:all).collect {|group| group.cn}
383
+ => ["root", "daemon", "bin", "sys", "adm", "tty", ..., "develop"]
384
+ </pre>
385
+
386
+ Here .find(:all) returns all matches to the same query. Both .find(:first) and
387
+ .find(:all) also can take more expressive arguments:
388
+
389
+ <pre>
390
+ irb> Group.find(:all, :attribute => 'gidNumber', :value => '1003').collect {|group| group.cn}
391
+ => ["develop"]
392
+ </pre>
393
+
394
+ So it is pretty clear what :attribute and :value do - they are used to query as
395
+ :attribute=:value.
396
+
397
+ If :attribute is unspecified, it defaults to the dn_attribute.
398
+
399
+ It is also possible to override :attribute and :value by specifying :filter. This
400
+ argument allows the direct specification of a LDAP filter to retrieve objects by.
401
+
402
+ h4. .search
403
+
404
+ .search is a class method that is accessible from any subclass of Base, and Base.
405
+ It lets the user perform an arbitrary search against the current LDAP connection
406
+ irrespetive of LDAP mapping data. This is meant to be useful as a utility method
407
+ to cover 80% of the cases where a user would want to use Base.connection directly.
408
+
409
+ <pre>
410
+ irb> Base.search(:base => 'dc=example,dc=com', :filter => '(uid=roo*)',
411
+ :scope => :sub, :attributes => ['uid', 'cn'])
412
+ => [["uid=root,ou=People,dc=dataspill,dc=org",{"cn"=>["root"], "uidNumber"=>["0"]}]
413
+ </pre>
414
+
415
+ You can specify the :filter, :base, :scope, and :attributes, but they all have defaults --
416
+ * :filter defaults to objectClass=* - usually this isn't what you want
417
+ * :base defaults to the base of the class this is executed from (as set in ldap_mapping)
418
+ * :scope defaults to :sub. Usually you won't need to change it (You can choose value also from between :one and :base)
419
+ * :attributes defaults to [] and is the list of attributes you want back. Empty means all of them.
420
+
421
+ h4. #valid?
422
+
423
+ valid? is a method that verifies that all attributes that are required by the
424
+ objects current objectClasses are populated.
425
+
426
+ h4. #save
427
+
428
+ save is a method that writes any changes to an object back to the LDAP server.
429
+ It automatically handles the addition of new objects, and the modification of
430
+ existing ones.
431
+
432
+ h4. .exists?
433
+
434
+ exists? is a simple method which returns true is the current object exists in
435
+ LDAP, or false if it does not.
436
+
437
+ <pre>
438
+ irb> User.exists?("dshadsadsa")
439
+ => false
440
+ </pre>
441
+
442
+
443
+ h3. ActiveLdap::Base
444
+
445
+ ActiveLdap::Base has come up a number of times in the examples above. Every
446
+ time, it was being used as the super class for the wrapper objects. While this
447
+ is it's main purpose, it also handles quite a bit more in the background.
448
+
449
+ h4. What is it?
450
+
451
+ ActiveLdap::Base is the heart of ActiveLdap. It does all the schema
452
+ parsing for validation and attribute-to-method mangling as well as manage the
453
+ connection to LDAP.
454
+
455
+ h5. setup_connection
456
+
457
+ Base.setup_connection takes many (optional) arguments and is used to
458
+ connect to the LDAP server. Sometimes you will want to connect anonymously
459
+ and other times over TLS with user credentials. Base.setup_connection is
460
+ here to do all of that for you.
461
+
462
+
463
+ By default, if you call any subclass of Base, such as Group, it will call
464
+ Base.setup_connection() if these is no active LDAP connection. If your
465
+ server allows anonymous binding, and you only want to access data in a
466
+ read-only fashion, you won't need to call Base.setup_connection. Here
467
+ is a fully parameterized call:
468
+
469
+ <pre>
470
+ Base.setup_connection(
471
+ :host => 'ldap.dataspill.org',
472
+ :port => 389,
473
+ :base => 'dc=dataspill,dc=org',
474
+ :logger => logger_object,
475
+ :bind_dn => "uid=drewry,ou=People,dc=dataspill,dc=org",
476
+ :password_block => Proc.new { 'password12345' },
477
+ :allow_anonymous => false,
478
+ :try_sasl => false
479
+ )
480
+ </pre>
481
+
482
+ There are quite a few arguments, but luckily many of them have safe defaults:
483
+ * :host defaults to "127.0.0.1".
484
+ * :port defaults to nil. 389 is applied if not specified.
485
+ * :bind_dn defaults to nil. anonymous binding is applied if not specified.
486
+ * :logger defaults to a Logger object that prints fatal messages to stderr
487
+ * :password_block defaults to nil
488
+ * :allow_anonymous defaults to true
489
+ * :try_sasl defaults to false - see Advanced Topics for more on this one.
490
+
491
+
492
+ Most of these are obvious, but I'll step through them for completeness:
493
+ * :host defines the LDAP server hostname to connect to.
494
+ * :port defines the LDAP server port to connect to.
495
+ * :method defines the type of connection - :tls, :ssl, :plain
496
+ * :base specifies the LDAP search base to use with the prefixes defined in all
497
+ subclasses.
498
+ * :bind_dn specifies what your server expects when attempting to bind with
499
+ credentials.
500
+ * :logger accepts a custom logger object to integrate with any other logging
501
+ your application uses.
502
+ * :password_block, if defined, give the Proc block for acquiring the password
503
+ * :password, if defined, give the user's password as a String
504
+ * :store_password indicates whether the password should be stored, or if used
505
+ whether the :password_block should be called on each reconnect.
506
+ * :allow_anonymous determines whether anonymous binding is allowed if other
507
+ bind methods fail
508
+ * :try_sasl, when true, tells ActiveLdap to attempt a SASL-GSSAPI bind
509
+ * :sasl_quiet, when true, tells the SASL libraries to not spew messages to STDOUT
510
+ * :sasl_options, if defined, should be a hash of options to pass through. This currently only works with the ruby-ldap adapter, which currently only supports :realm, :authcid, and :authzid.
511
+ * :retry_limit - indicates the number of attempts to reconnect that will be undertaken when a stale connection occurs. -1 means infinite.
512
+ * :retry_wait - seconds to wait before retrying a connection
513
+ * :scope - dictates how to find objects. (Default: :one)
514
+ * :timeout - time in seconds - defaults to disabled. This CAN interrupt search() requests. Be warned.
515
+ * :retry_on_timeout - whether to reconnect when timeouts occur. Defaults to true
516
+ See lib/configuration.rb(ActiveLdap::Configuration::DEFAULT_CONFIG) for defaults for each option
517
+
518
+ Base.setup_connection just setups connection
519
+ configuration. A connection is connected and bound when it
520
+ is needed. It follows roughly the following approach:
521
+
522
+ * Connect to host:port using :method
523
+
524
+ * If bind_dn and password_block/password, attempt to bind with credentials.
525
+ * If that fails or no password_block and anonymous allowed, attempt to bind
526
+ anonymously.
527
+ * If that fails, error out.
528
+
529
+ On connect, the configuration options passed in are stored
530
+ in an internal class variable which is used to cache the
531
+ information without ditching the defaults passed in from
532
+ configuration.rb
533
+
534
+ h5. connection
535
+
536
+ Base.connection returns the ActiveLdap::Connection object.
537
+
538
+ h3. Exceptions
539
+
540
+ There are a few custom exceptions used in ActiveLdap. They are detailed below.
541
+
542
+ h4. DeleteError
543
+
544
+ This exception is raised when #delete fails. It will include LDAP error
545
+ information that was passed up during the error.
546
+
547
+ h4. SaveError
548
+
549
+ This exception is raised when there is a problem in #save updating or creating
550
+ an LDAP entry. Often the error messages are cryptic. Looking at the server
551
+ logs or doing an "Wireshark":http://www.wireshark.org dump of the connection will
552
+ often provide better insight.
553
+
554
+ h4. AuthenticationError
555
+
556
+ This exception is raised during Base.setup_connection if no valid authentication methods
557
+ succeeded.
558
+
559
+ h4. ConnectionError
560
+
561
+ This exception is raised during Base.setup_connection if no valid
562
+ connection to the LDAP server could be created. Check you
563
+ Base.setup_connection arguments, and network connectivity! Also check
564
+ your LDAP server logs to see if it ever saw the request.
565
+
566
+ h4. ObjectClassError
567
+
568
+ This exception is raised when an object class is used that is not defined
569
+ in the schema.
570
+
571
+ h3. Others
572
+
573
+ Other exceptions may be raised by the Ruby/LDAP module, or by other subsystems.
574
+ If you get one of these exceptions and think it should be wrapped, write me an
575
+ email and let me know where it is and what you expected. For faster results,
576
+ email a patch!
577
+
578
+ h3. Putting it all together
579
+
580
+ Now that all of the components of ActiveLdap have been covered, it's time
581
+ to put it all together! The rest of this section will show the steps to setup
582
+ example user and group management scripts for use with the LDAP tree described
583
+ above.
584
+
585
+ All of the scripts here are in the package's examples/ directory.
586
+
587
+ h4. Setting up
588
+
589
+ Create directory for scripts.
590
+
591
+ <pre>
592
+ mkdir -p ldapadmin/objects
593
+ </pre>
594
+
595
+ In ldapadmin/objects/ create the file user.rb:
596
+
597
+ <pre>
598
+ require 'objects/group'
599
+
600
+ class User < ActiveLdap::Base
601
+ ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People', :classes => ['person', 'posixAccount']
602
+ belongs_to :groups, :class_name => 'Group', :many => 'memberUid'
603
+ end
604
+ </pre>
605
+
606
+ In ldapadmin/objects/ create the file group.rb:
607
+
608
+ <pre>
609
+ class Group < ActiveLdap::Base
610
+ ldap_mapping :classes => ['top', 'posixGroup'], :prefix => 'ou=Groups'
611
+ has_many :members, :class_name => "User", :wrap => "memberUid"
612
+ has_many :primary_members, :class_name => 'User', :foreign_key => 'gidNumber', :primary_key => 'gidNumber'
613
+ end
614
+ </pre>
615
+
616
+ Now, we can write some small scripts to do simple management tasks.
617
+
618
+ h4. Creating LDAP entries
619
+
620
+ Now let's create a really dumb script for adding users - ldapadmin/useradd:
621
+
622
+ <pre>
623
+ #!/usr/bin/ruby -W0
624
+
625
+ base = File.expand_path(File.join(File.dirname(__FILE__), ".."))
626
+ $LOAD_PATH << File.join(base, "lib")
627
+ $LOAD_PATH << File.join(base, "examples")
628
+
629
+ require 'rubygems'
630
+ require 'active_ldap'
631
+ require 'objects/user'
632
+ require 'objects/group'
633
+
634
+ argv, opts, options = ActiveLdap::Command.parse_options do |opts, options|
635
+ opts.banner += " USER_NAME CN UID"
636
+ end
637
+
638
+ if argv.size == 3
639
+ name, cn, uid = argv
640
+ else
641
+ $stderr.puts opts
642
+ exit 1
643
+ end
644
+
645
+ pwb = Proc.new do |user|
646
+ ActiveLdap::Command.read_password("[#{user}] Password: ")
647
+ end
648
+
649
+ ActiveLdap::Base.setup_connection(:password_block => pwb,
650
+ :allow_anonymous => false)
651
+
652
+ if User.exists?(name)
653
+ $stderr.puts("User #{name} already exists.")
654
+ exit 1
655
+ end
656
+
657
+ user = User.new(name)
658
+ user.add_class('shadowAccount')
659
+ user.cn = cn
660
+ user.uid_number = uid
661
+ user.gid_number = uid
662
+ user.home_directory = "/home/#{name}"
663
+ user.sn = "somesn"
664
+ unless user.save
665
+ puts "failed"
666
+ puts user.errors.full_messages
667
+ exit 1
668
+ end
669
+ </pre>
670
+
671
+ h4. Managing LDAP entries
672
+
673
+ Now let's create another dumb script for modifying users - ldapadmin/usermod:
674
+
675
+ <pre>
676
+ #!/usr/bin/ruby -W0
677
+
678
+ base = File.expand_path(File.join(File.dirname(__FILE__), ".."))
679
+ $LOAD_PATH << File.join(base, "lib")
680
+ $LOAD_PATH << File.join(base, "examples")
681
+
682
+ require 'rubygems'
683
+ require 'active_ldap'
684
+ require 'objects/user'
685
+ require 'objects/group'
686
+
687
+ argv, opts, options = ActiveLdap::Command.parse_options do |opts, options|
688
+ opts.banner += " USER_NAME CN UID"
689
+ end
690
+
691
+ if argv.size == 3
692
+ name, cn, uid = argv
693
+ else
694
+ $stderr.puts opts
695
+ exit 1
696
+ end
697
+
698
+ pwb = Proc.new do |user|
699
+ ActiveLdap::Command.read_password("[#{user}] Password: ")
700
+ end
701
+
702
+ ActiveLdap::Base.setup_connection(:password_block => pwb,
703
+ :allow_anonymous => false)
704
+
705
+ unless User.exists?(name)
706
+ $stderr.puts("User #{name} doesn't exist.")
707
+ exit 1
708
+ end
709
+
710
+ user = User.find(name)
711
+ user.cn = cn
712
+ user.uid_number = uid
713
+ user.gid_number = uid
714
+ unless user.save
715
+ puts "failed"
716
+ puts user.errors.full_messages
717
+ exit 1
718
+ end
719
+ </pre>
720
+
721
+ h4. Removing LDAP entries
722
+
723
+ Now let's create more one for deleting users - ldapadmin/userdel:
724
+
725
+ <pre>
726
+ #!/usr/bin/ruby -W0
727
+
728
+ base = File.expand_path(File.join(File.dirname(__FILE__), ".."))
729
+ $LOAD_PATH << File.join(base, "lib")
730
+ $LOAD_PATH << File.join(base, "examples")
731
+
732
+ require 'rubygems'
733
+ require 'active_ldap'
734
+ require 'objects/user'
735
+ require 'objects/group'
736
+
737
+ argv, opts, options = ActiveLdap::Command.parse_options do |opts, options|
738
+ opts.banner += " USER_NAME"
739
+ end
740
+
741
+ if argv.size == 1
742
+ name = argv.shift
743
+ else
744
+ $stderr.puts opts
745
+ exit 1
746
+ end
747
+
748
+ pwb = Proc.new do |user|
749
+ ActiveLdap::Command.read_password("[#{user}] Password: ")
750
+ end
751
+
752
+ ActiveLdap::Base.setup_connection(:password_block => pwb,
753
+ :allow_anonymous => false)
754
+
755
+ unless User.exists?(name)
756
+ $stderr.puts("User #{name} doesn't exist.")
757
+ exit 1
758
+ end
759
+
760
+ User.destroy(name)
761
+ </pre>
762
+
763
+ h3. Advanced Topics
764
+
765
+ Below are some situation tips and tricks to get the most out of ActiveLdap.
766
+
767
+
768
+ h4. Binary data and other subtypes
769
+
770
+ Sometimes, you may want to store attributes with language specifiers, or
771
+ perhaps in binary form. This is (finally!) fully supported. To do so,
772
+ follow the examples below:
773
+
774
+ <pre>
775
+ irb> user = User.new('drewry')
776
+ => ...
777
+ # This adds a cn entry in lang-en and whatever the server default is.
778
+ irb> user.cn = [ 'wad', {'lang-en' => ['wad', 'Will Drewry']} ]
779
+ => ...
780
+ irb> user.cn
781
+ => ["wad", {"lang-en-us" => ["wad", "Will Drewry"]}]
782
+ # Now let's add a binary X.509 certificate (assume objectClass is correct)
783
+ irb> user.user_certificate = File.read('example.der')
784
+ => ...
785
+ irb> user.save
786
+ </pre>
787
+
788
+ So that's a lot to take in. Here's what is going on. I just set the LDAP
789
+ object's cn to "wad" and cn:lang-en-us to ["wad", "Will Drewry"].
790
+ Anytime a LDAP subtype is required, you must encapsulate the data in a Hash.
791
+
792
+ But wait a minute, I just read in a binary certificate without wrapping it up.
793
+ So any binary attribute _that requires ;binary subtyping_ will automagically
794
+ get wrapped in @{'binary' => value}@ if you don't do it. This keeps your #writes
795
+ from breaking, and my code from crying. For correctness, I could have easily
796
+ done the following:
797
+
798
+ <pre>
799
+ irb> user.user_certificate = {'binary' => File.read('example.der')}
800
+ </pre>
801
+
802
+ You should note that some binary data does not use the binary subtype all the time.
803
+ One example is jpegPhoto. You can use it as jpegPhoto;binary or just as jpegPhoto.
804
+ Since the schema dictates that it is a binary value, ActiveLdap will write
805
+ it as binary, but the subtype will not be automatically appended as above. The
806
+ use of the subtype on attributes like jpegPhoto is ultimately decided by the
807
+ LDAP site policy and not by any programmatic means.
808
+
809
+ The only subtypes defined in LDAPv3 are lang-* and binary. These can be nested
810
+ though:
811
+
812
+ <pre>
813
+ irb> user.cn = [{'lang-ja' => {'binary' => 'some Japanese'}}]
814
+ </pre>
815
+
816
+ As I understand it, OpenLDAP does not support nested subtypes, but some
817
+ documentation I've read suggests that Netscape's LDAP server does. I only
818
+ have access to OpenLDAP. If anyone tests this out, please let me know how it
819
+ goes!
820
+
821
+
822
+ And that pretty much wraps up this section.
823
+
824
+ h4. Further integration with your environment aka namespacing
825
+
826
+ If you want this to cleanly integrate into your system-wide Ruby include path,
827
+ you should put your extension classes inside a custom module.
828
+
829
+
830
+ Example:
831
+
832
+ ./myldap.rb:
833
+
834
+ <pre>
835
+ require 'active_ldap'
836
+ require 'myldap/user'
837
+ require 'myldap/group'
838
+ module MyLDAP
839
+ end
840
+ </pre>
841
+
842
+ ./myldap/user.rb:
843
+
844
+ <pre>
845
+ module MyLDAP
846
+ class User < ActiveLdap::Base
847
+ ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People', :classes => ['top', 'account', 'posixAccount']
848
+ belongs_to :groups, :class_name => 'MyLDAP::Group', :many => 'memberUid'
849
+ end
850
+ end
851
+ </pre>
852
+
853
+ ./myldap/group.rb:
854
+
855
+ <pre>
856
+ module MyLDAP
857
+ class Group < ActiveLdap::Base
858
+ ldap_mapping :classes => ['top', 'posixGroup'], :prefix => 'ou=Groups'
859
+ has_many :members, :class_name => 'MyLDAP::User', :wrap => 'memberUid'
860
+ has_many :primary_members, :class_name => 'MyLDAP::User', :foreign_key => 'gidNumber', :primary_key => 'gidNumber'
861
+ end
862
+ end
863
+ </pre>
864
+
865
+ Now in your local applications, you can call
866
+
867
+ <pre>
868
+ require 'myldap'
869
+
870
+ MyLDAP::Group.new('foo')
871
+ ...
872
+ </pre>
873
+
874
+ and everything should work well.
875
+
876
+
877
+ h4. force array results for single values
878
+
879
+ Even though ActiveLdap attempts to maintain programmatic ease by
880
+ returning Array values only. By specifying 'true' as an argument to
881
+ any attribute method you will get back a Array if it is single value.
882
+ Here's an example:
883
+
884
+ <pre>
885
+ irb> user = User.new('drewry')
886
+ => ...
887
+ irb> user.cn(true)
888
+ => ["Will Drewry"]
889
+ </pre>
890
+
891
+ h4. Dynamic attribute crawling
892
+
893
+ If you use tab completion in irb, you'll notice that you /can/ tab complete the dynamic
894
+ attribute methods. You can still see which methods are for attributes using
895
+ Base#attribute_names:
896
+
897
+ <pre>
898
+ irb> d = Group.new('develop')
899
+ => ...
900
+ irb> d.attribute_names
901
+ => ["gidNumber", "cn", "memberUid", "commonName", "description", "userPassword", "objectClass"]
902
+ </pre>
903
+
904
+
905
+ h4. Juggling multiple LDAP connections
906
+
907
+ In the same vein as the last tip, you can use multiple LDAP connections by
908
+ per class as follows:
909
+
910
+ <pre>
911
+ irb> anon_class = Class.new(Base)
912
+ => ...
913
+ irb> anon_class.setup_connection
914
+ => ...
915
+ irb> auth_class = Class.new(Base)
916
+ => ...
917
+ irb> auth_class.setup_connection(:password_block => lambda{'mypass'})
918
+ => ...
919
+ </pre>
920
+
921
+ This can be useful for doing authentication tests and other such tricks.
922
+
923
+ h4. :try_sasl
924
+
925
+ If you have the Ruby/LDAP package with the SASL/GSSAPI patch from Ian
926
+ MacDonald's web site, you can use Kerberos to bind to your LDAP server. By
927
+ default, :try_sasl is false.
928
+
929
+ Also note that you must be using OpenLDAP 2.1.29 or higher to use SASL/GSSAPI
930
+ due to some bugs in older versions of OpenLDAP.
931
+
932
+ h4. Don't be afraid! [Internals]
933
+
934
+ Don't be afraid to add more methods to the extensions classes and to
935
+ experiment. That's exactly how I ended up with this package. If you come up
936
+ with something cool, please share it!
937
+
938
+ The internal structure of ActiveLdap::Base, and thus all its subclasses, is
939
+ still in flux. I've tried to minimize the changes to the overall API, but
940
+ the internals are still rough around the edges.
941
+
942
+ h5. Where's ldap_mapping data stored? How can I get to it?
943
+
944
+ When you call ldap_mapping, it overwrites several class methods inherited
945
+ from Base:
946
+ * Base.base()
947
+ * Base.required_classes()
948
+ * Base.dn_attribute()
949
+
950
+ You can access these from custom class methods by calling MyClass.base(),
951
+ or whatever. There are predefined instance methods for getting to these
952
+ from any new instance methods you define:
953
+ * Base#base()
954
+ * Base#required_classes()
955
+ * Base#dn_attribute()
956
+
957
+ h5. What else?
958
+
959
+ Well if you want to use the LDAP connection for anything, I'd suggest still
960
+ calling Base.connection to get it. There really aren't many other internals
961
+ that need to be worried about. You could get the LDAP schema with
962
+ Base.schema.
963
+
964
+ The only other useful tricks are dereferencing and accessing the stored
965
+ data. Since LDAP attributes can have multiple names, e.g. cn or commonName,
966
+ any methods you write might need to figure it out. I'd suggest just
967
+ calling self[attribname] to get the value, but if that's not good enough,
968
+ you can call look up the stored name by #to_real_attribute_name as follows:
969
+
970
+ <pre>
971
+ irb> User.find(:first).instance_eval do
972
+ irb> to_real_attribute_name('commonName')
973
+ irb> end
974
+ => 'cn'
975
+ </pre>
976
+
977
+ This tells you the name the attribute is stored in behind the scenes (@data).
978
+ Again, self[attribname] should be enough for most extensions, but if not,
979
+ it's probably safe to dabble here.
980
+
981
+ Also, if you like to look up all aliases for an attribute, you can call the
982
+ following:
983
+
984
+ <pre>
985
+ irb> User.schema.attribute_type 'cn', 'NAME'
986
+ => ["cn", "commonName"]
987
+ </pre>
988
+
989
+ This is discovered automagically from the LDAP server's schema.
990
+
991
+ h2. Limitations
992
+
993
+ h3. Speed
994
+
995
+ Currently, ActiveLdap could be faster. I have some recursive type
996
+ checking going on which slows object creation down, and I'm sure there
997
+ are many, many other places optimizations can be done. Feel free
998
+ to send patches, or just hang in there until I can optimize away the
999
+ slowness.
1000
+
1001
+ h2. Feedback
1002
+
1003
+ Any and all feedback and patches are welcome. I am very excited about this
1004
+ package, and I'd like to see it prove helpful to more people than just myself.
1005
+