ruby-activeldap 0.4.1

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.
data/lib/activeldap.rb ADDED
@@ -0,0 +1,889 @@
1
+ #!/usr/bin/ruby -W0
2
+ # = Ruby/ActiveLDAP
3
+ #
4
+ # "Ruby/ActiveLDAP" Copyright (C) 2004 Will Drewry mailto:will@alum.bu.edu
5
+ # Documentation Version 0.3
6
+ #
7
+ # == Introduction
8
+ #
9
+ # Ruby/ActiveLDAP is a novel way of interacting with LDAP. Most interaction with
10
+ # LDAP is done using clunky LDIFs, web interfaces, or with painful APIs that
11
+ # required a thick reference manual nearby. Ruby/ActiveLDAP aims to fix that.
12
+ # Inspired by ActiveRecord[http://activerecord.rubyonrails.com], Ruby/ActiveLDAP provides an
13
+ # object oriented interface to LDAP entries.
14
+ #
15
+ # The target audience is system administrators and LDAP users everywhere that
16
+ # need quick, clean access to LDAP in Ruby.
17
+ #
18
+ # === What's LDAP?
19
+ #
20
+ # LDAP stands for "Lightweight Directory Access Protocol." Basically this means
21
+ # that it is the protocol used for accessing LDAP servers. LDAP servers
22
+ # lightweight directories. An LDAP server can contain anything from a simple
23
+ # digital phonebook to user accounts for computer systems. More and more
24
+ # frequently, it is being used for the latter. My examples in this text will
25
+ # assume some familiarity with using LDAP as a centralized authentication and
26
+ # authorization server for Unix systems. (Unfortunately, I've yet to try this
27
+ # against Microsoft's ActiveDirectory, despite what the name implies.)
28
+ #
29
+ # Further reading:
30
+ # * RFC1777[http://www.faqs.com/rfcs/rfc1777.html] - Lightweight Directory Access Protocol
31
+ # * OpenLDAP[http://www.openldap.com]
32
+ #
33
+ # === So why use Ruby/ActiveLDAP?
34
+ #
35
+ # Well if you like to fumble around in the dark, dank innards of LDAP, you can
36
+ # quit reading now. However, if you'd like a cleaner way to integrate LDAP in to
37
+ # your existing code, hopefully that's why you'll want to use Ruby/ActiveLDAP.
38
+ #
39
+ # Using LDAP directly (even with the excellent Ruby/LDAP), leaves you bound to
40
+ # the world of the predefined LDAP API. While this API is important for many
41
+ # reasons, having to extract code out of LDAP search blocks and create huge
42
+ # arrays of LDAP.mod entries make code harder to read, less intuitive, and just
43
+ # less fun to write. Hopefully, Ruby/ActiveLDAP will remedy all of these
44
+ # problems!
45
+ #
46
+ # == Getting Started
47
+ #
48
+ # Ruby/ActiveLDAP does have some overhead when you get started. You must not
49
+ # only install the package and all of it's requirements, but you must also make
50
+ # customizations that will let it work in your environment.
51
+ #
52
+ # === Requirements
53
+ #
54
+ # * Ruby[http://www.ruby-lang.com] 1.8.x
55
+ # * Ruby/LDAP[http://ruby-ldap.sourcefcome.net]
56
+ # * Log4r[http://log4r.sourcefcome.net]
57
+ # * (Optional) Ruby/LDAP+GSSAPI[http://caliban.com/files/redhat/RPMS/i386/ruby-ldap-0.8.2-4.i386.rpm]
58
+ # * An LDAP server compatible with Ruby/LDAP: OpenLDAP[http://www.openldap.com], etc
59
+ # - Your LDAP server must allow root_dse queries to allow for schema queries
60
+ # * Examples also require: Ruby/Password[http://raa.ruby-lang.com/project/ruby-password/]
61
+ #
62
+ # === Installation
63
+ #
64
+ # Assuming all the requirements are installed, you can install by grabbing the latest tgz file from
65
+ # the download site[http://projects.example.com/libraries/ruby/activeldap/download.html].
66
+ #
67
+ # The following steps will get the Ruby/ActiveLDAP installed in no time!
68
+ #
69
+ # $ tar -xzvf ruby-activeldap-current.tgz
70
+ # $ cd ruby-activeldap-VERSION
71
+ #
72
+ # Edit lib/activeldap/configuration.rb replacing values to match what will work
73
+ # with your LDAP servers. Please note that those variables are required, but can
74
+ # be overridden in any program as detailed later in this document. Also make
75
+ # sure that "ROOT" stays all upcase.
76
+ #
77
+ # Now run:
78
+ #
79
+ # $ ruby setup.rb config
80
+ # $ ruby setup.rb setup
81
+ # $ (as root) ruby setup.rb install
82
+ #
83
+ # Now as a quick test, you can run:
84
+ #
85
+ # $ irb
86
+ # irb> require 'activeldap'
87
+ # => true
88
+ # irb> exit
89
+ #
90
+ # If the require returns false or an exception is raised, there has been a
91
+ # problem with the installation. You may need to customize what setup.rb does on
92
+ # install.
93
+ #
94
+ #
95
+ # === Customizations
96
+ #
97
+ # Now that Ruby/ActiveLDAP is installed and working, we still have a few more
98
+ # steps to make it useful for programming.
99
+ #
100
+ # Let's say that you are writing a Ruby program for managing user and group
101
+ # accounts in LDAP. I will use this as the running example throughout the
102
+ # document.
103
+ #
104
+ # You will want to make a directory called 'ldapadmin' wherever is convenient. Under this directory,
105
+ # you'll want to make sure you have a 'lib' directory.
106
+ #
107
+ # $ cd ~
108
+ # $ mkdir ldapadmin
109
+ # $ cd ldapadmin
110
+ # $ mkdir lib
111
+ # $ cd lib
112
+ #
113
+ # The lib directory is where we'll be making customizations. You can, of course,
114
+ # make this changes somewhere in Ruby's default search path to make this
115
+ # accessible to every Ruby scripts. Enough of my babbling, I'm sure you'd like to
116
+ # know what we're going to put in lib/.
117
+ #
118
+ # We're going to put extension classes in there. What are extension classes you say . . .
119
+ #
120
+ #
121
+ # == Usage
122
+ #
123
+ # This section covers using Ruby/ActiveLDAP from writing extension classes to
124
+ # writing applications that use them.
125
+ #
126
+ # Just to give a taste of what's to come, here is a quick example using irb:
127
+ #
128
+ # irb> require 'activeldap'
129
+ #
130
+ # Here's an extension class that maps to the LDAP Group objects:
131
+ #
132
+ # irb> class Group < ActiveLDAP::Base
133
+ # irb* ldap_mapping
134
+ # irb* end
135
+ #
136
+ # Here is the Group class in use:
137
+ #
138
+ # irb> all_groups = Group.find_all('*')
139
+ # => ["root", "daemon", "bin", "sys", "adm", "tty", ..., "develop"]
140
+ #
141
+ # irb> group = Group.new("develop")
142
+ # => #<Group:0x..........>
143
+ #
144
+ # irb> group.members
145
+ # => ["drewry"]
146
+ #
147
+ # irb> group.cn
148
+ # => "develop"
149
+ #
150
+ # irb> group.gidNumber
151
+ # => "1003"
152
+ #
153
+ # That's it! No let's get back in to it.
154
+ #
155
+ # === Extension Classes
156
+ #
157
+ # Extension classes are classes that are subclassed from ActiveLDAP::Base. They
158
+ # are used to represent objects in your LDAP server abstractly.
159
+ #
160
+ # ==== Why do I need them?
161
+ #
162
+ # Extension classes are what make Ruby/ActiveLDAP "active"! They do all the
163
+ # background work to make easy-to-use objects by mapping the LDAP object's
164
+ # attributes on to a Ruby class.
165
+ #
166
+ #
167
+ # ==== Special Methods
168
+ #
169
+ # I will briefly talk about each of the methods you can use when defining an
170
+ # extension class. In the above example, I only made one special method call
171
+ # inside the Group class. More than likely, you will want to more than that.
172
+ #
173
+ # ===== ldap_mapping
174
+ #
175
+ # ldap_mapping is the only required method to setup an extension class for use
176
+ # with Ruby/ActiveLDAP. It must be called inside of a subclass as shown above.
177
+ #
178
+ # Below is a much more realistic Group class:
179
+ #
180
+ # class Group < ActiveLDAP::Base
181
+ # ldap_mapping :dnattr => 'cn', :prefix => 'ou=Groups', :classes => ['top', 'posixGroup']
182
+ # end
183
+ #
184
+ # As you can see, this method is used for defining how this class maps in to LDAP. Let's say that
185
+ # my LDAP tree looks something like this:
186
+ #
187
+ # * dc=example,dc=com
188
+ # |- ou=People,dc=example,dc=com
189
+ # |+ ou=Groups,dc=example,dc=com
190
+ # \
191
+ # |- cn=develop,ou=Groups,dc=example,dc=com
192
+ # |- cn=root,ou=Groups,dc=example,dc=com
193
+ # |- ...
194
+ #
195
+ # Under ou=People I store user objects, and under ou=Groups, I store group
196
+ # objects. What |ldap_mapping| has done is mapped the class in to the LDAP tree
197
+ # abstractly. With the given :dnattr and :prefix, it will only work for entries
198
+ # under ou=Groups,dc=example,dc=com using the primary attribute 'cn' as the
199
+ # beginning of the distinguished name.
200
+ #
201
+ # Just for clarity, here's how the arguments map out:
202
+ #
203
+ # cn=develop,ou=Groups,dc=example,dc=com
204
+ # ^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
205
+ # :dnattr | |
206
+ # :prefix |
207
+ # :base from configuration.rb
208
+ #
209
+ #
210
+ # Something's missing: :classes. :classes is used to tell Ruby/ActiveLDAP what
211
+ # the minimum requirement is when creating a new object. LDAP uses objectClasses
212
+ # to define what attributes a LDAP object may have. Ruby/ActiveLDAP needs to know
213
+ # what classes are required when creating a new object. Of course, you can leave
214
+ # that field out to default to ['top'] only. Then you can let each application
215
+ # choose what objectClasses their objects should have by calling the method e.g.
216
+ # Group#objectClass=(value).
217
+ #
218
+ # :classes isn't the only optional argument. If :dnattr is left off, it defaults
219
+ # to 'cn'. If :prefix is left off, it will default to 'ou=CLASSNAME'. In this
220
+ # case, it would be 'ou=Group'.
221
+ #
222
+ # :classes should be an Array. :dnattr should be a String and so should :prefix.
223
+ #
224
+ #
225
+ # ===== belongs_to
226
+ #
227
+ # This method allows an extension class to make use of other extension classes
228
+ # tying objects together across the LDAP tree. Often, user objects will be
229
+ # members of, or belong_to, Group objects.
230
+ #
231
+ # * dc=example,dc=com
232
+ # |+ ou=People,dc=example,dc=com
233
+ # \
234
+ # |- uid=drewry,ou=People,dc=example,dc=com
235
+ # |- ou=Groups,dc=example,dc=com
236
+ #
237
+ #
238
+ # In the above tree, one such example would be user 'drewry' who is a part of the
239
+ # group 'develop'. You can see this by looking at the 'memberUid' field of 'develop'.
240
+ #
241
+ # irb> develop = Group.new('develop')
242
+ # => ...
243
+ # irb> develop.memberUid
244
+ # => ['drewry', 'builder']
245
+ #
246
+ # If we look at the LDAP entry for 'drewry', we do not see any references to
247
+ # group 'develop'. In order to remedy that, we can use belongs_to
248
+ #
249
+ # irb> class User < ActiveLDAP::Base
250
+ # irb* ldap_mapping :dnattr => 'uid', :prefix => 'People', :classes => ['top','account']
251
+ # irb* belongs_to :groups, :class_name => 'Group', :foreign_key => 'memberUid', :local_key => 'uid'
252
+ # irb* end
253
+ #
254
+ # Now, class User will have a method called 'groups' which will retrieve all
255
+ # Group objects that a user is in.
256
+ #
257
+ # irb> me = User.new('drewry')
258
+ # => ...
259
+ # irb> me.groups
260
+ # => ["cdrom", "audio", "develop"]
261
+ #
262
+ # Methods created with belongs_to also take an optional argument: objects. This
263
+ # argument specifies whether it will return the value of the 'dnattr' of the
264
+ # objects, or whether it will return Group objects.
265
+ #
266
+ # irb> me.groups(true)
267
+ # => [#<Group:0x000001 ...>, #<Group:0x000002 ...>, ...]
268
+ # irb> me.groups(true).each { |group| p group.cn };nil
269
+ # "cdrom"
270
+ # "audio"
271
+ # "develop"
272
+ # => nil
273
+ #
274
+ # (Note: nil is just there to make the output cleaner...)
275
+ #
276
+ # TIP: If you weren't sure what the distinguished name attribute was for Group,
277
+ # you could also do the following:
278
+ #
279
+ # irb> me.groups(true).each { |group| p group.dnattr };nil
280
+ # "cdrom"
281
+ # "audio"
282
+ # "develop"
283
+ # => nil
284
+ #
285
+ # Now let's talk about the arguments. The first argument is the name of the
286
+ # method you wish to create. In this case, we created a method called groups
287
+ # using the symbol :groups. The next collection of arguments are actually a Hash
288
+ # (as with ldap_mapping). :class_name should be a string that has the name of a
289
+ # class you've already included. If you class is inside of a module, be sure to
290
+ # put the whole name, e.g. :class_name => "MyLdapModule::Group". :foreign_key
291
+ # tells belongs_to what attribute Group objects have that match the :local_key.
292
+ # :local_key is the name of the local attribute whose value should be looked up
293
+ # in Group under the foreign key. If :local_key is left off of the argument list,
294
+ # it is assumed to be the dnattr. With this in mind, the above definition could
295
+ # become:
296
+ #
297
+ # irb> class User < ActiveLDAP::Base
298
+ # irb* ldap_mapping :dnattr => 'uid', :prefix => 'People', :classes => ['top','account']
299
+ # irb* belongs_to :groups, :class_name => 'Group', :foreign_key => 'memberUid'
300
+ # irb* end
301
+ #
302
+ # In addition, you can do simple membership tests by doing the following:
303
+ #
304
+ # irb> me.groups.member? 'root'
305
+ # => false
306
+ # irb> me.groups.member? 'develop'
307
+ # => true
308
+ #
309
+ # ===== has_many
310
+ #
311
+ # This method is the opposite of belongs_to. Instead of checking other objects in
312
+ # other parts of the LDAP tree to see if you belong to them, you have multiple
313
+ # objects from other trees listed in your object. To show this, we can just
314
+ # invert the example from above:
315
+ #
316
+ # class Group < ActiveLDAP::Base
317
+ # ldap_mapping :dnattr => 'cn', :prefix => 'ou=Groups', :classes => ['top', 'posixGroup']
318
+ # has_many :members, :class_name => "User", :local_key => "memberUid", :foreign_key => 'uid'
319
+ # end
320
+ #
321
+ # Now we can see that group develop has user 'drewry' as a member, and it can
322
+ # even return all responses in object form just like belongs_to methods.
323
+ #
324
+ # irb> develop = Group.new('develop')
325
+ # => ...
326
+ # irb> develop.members
327
+ # => ["drewry", "builder"]
328
+ # irb> develop.members(true)
329
+ # => [#<User:0x000001 ...>, #<User:...>]
330
+ #
331
+ #
332
+ # The arguments for has_many follow the exact same idea that belongs_to's
333
+ # arguments followed. :local_key's contents are used to search for matching
334
+ # :foreign_key content. If :foreign_key is not specified, it defaults to the
335
+ # dnattr of the specified :class_name.
336
+ #
337
+ # === Using these new classes
338
+ #
339
+ # These new classes have many method calls. Many of them are automatically
340
+ # generated to provide access to the LDAP object's attributes. Other were defined
341
+ # during class creation by special methods like belongs_to. There are a few other
342
+ # methods that do not fall in to these categories.
343
+ #
344
+ #
345
+ # ==== .find and .find_all
346
+ #
347
+ # .find is a class method that is accessible from any subclass of Base that has
348
+ # 'ldap_mapping' called. When called it returns the first match of the given
349
+ # class.
350
+ #
351
+ # irb> Group.find('*')
352
+ # => "root"
353
+ #
354
+ # In this simple example, Group.find took the search string of 'deve*' and
355
+ # searched for the first match in Group where the dnattr matched the query. This
356
+ # is the simplest example of .find.
357
+ #
358
+ # irb> Group.find_all('*')
359
+ # => ["root", "daemon", "bin", "sys", "adm", "tty", ..., "develop"]
360
+ #
361
+ # Here .find_all returns all matches to the same query. Both .find and .find_all
362
+ # also can take more expressive arguments:
363
+ #
364
+ # irb> Group.find_all(:attribute => 'gidNumber', :value => '1003', :objects => false)
365
+ # => ["develop"]
366
+ #
367
+ # So it is pretty clear what :attribute and :value do - they are used to query as
368
+ # :attribute=:value. :objects is used to return precreated objects of the given
369
+ # Class when it is set to true.
370
+ #
371
+ # irb> Group.find_all(:attribute => 'gidNumber', :value => '1003', :objects => false)
372
+ # => [#<Group:0x40674a70 ..>]
373
+ #
374
+ # If :objects is unspecified, it defaults to false. If :attribute is unspecified,
375
+ # it defaults to the dnattr.
376
+ #
377
+ # ==== .search
378
+ # .search is a class method that is accessible from any subclass of Base, and Base.
379
+ # It lets the user perform an arbitrary search against the current LDAP connection
380
+ # irrespetive of LDAP mapping data. This is meant to be useful as a utility method
381
+ # to cover 80% of the cases where a user would want to use Base.connection directly.
382
+ #
383
+ # irb> Base.search(:base => 'dc=example,dc=com', :filter => '(uid=roo*)',
384
+ # :scope => LDAP::LDAP_SCOPE_SUBTREE, :attrs => ['uid', 'cn'])
385
+ # => [{"dn"=>"uid=root,ou=People,dc=example,dc=com","cn"=>["root"], "uidNumber"=>["0"]}]
386
+ # You can specify the :filter, :base, :scope, and :attrs, but they all have defaults --
387
+ # * :filter defaults to objectClass=* - usually this isn't what you want
388
+ # * :base defaults to the base of the class this is executed from (as set in ldap_mapping)
389
+ # * :scope defaults to LDAP::LDAP_SCOPE_SUBTREE. Usually you won't need to change it
390
+ # * :attrs defaults to [] and is the list of attrs you want back. Empty means all of them.
391
+ #
392
+ # ==== #validate
393
+ #
394
+ # validate is a method that verifies that all attributes that are required by the
395
+ # objects current objectClasses are populated. This is called by #write prior to
396
+ # performing any action. Its explicit use in an application is unnecessary, and
397
+ # it may become a private method in the future.
398
+ #
399
+ # ==== #write
400
+ #
401
+ # write is a method that writes any changes to an object back to the LDAP server.
402
+ # It automatically handles the addition of new objects, and the modification of
403
+ # existing ones.
404
+ #
405
+ # ==== #exists?
406
+ #
407
+ # exists? is a simple method which returns true is the current object exists in
408
+ # LDAP, or false if it does not.
409
+ #
410
+ # irb> newuser = User.new("dshadsadsa")
411
+ # => ...
412
+ # irb> newuser.exists?
413
+ # => false
414
+ #
415
+ #
416
+ # === ActiveLDAP::Base
417
+ #
418
+ # ActiveLDAP::Base has come up a number of times in the examples above. Every
419
+ # time, it was being used as the super class for the wrapper objects. While this
420
+ # is it's main purpose, it also handles quite a bit more in the background.
421
+ #
422
+ # ==== What is it?
423
+ #
424
+ # ActiveLDAP::Base is the heart of Ruby/ActiveLDAP. It does all the schema
425
+ # parsing for validation and attribute-to-method mangling as well as manage the
426
+ # connection to LDAP.
427
+ #
428
+ # ===== connect
429
+ #
430
+ # Base.connect takes many (optional) arguments and is used to connect to the LDAP
431
+ # server. Sometimes you will want to connect anonymously and other times over TLS
432
+ # with user credentials. Base.connect is here to do all of that for you.
433
+ #
434
+ #
435
+ # By default, if you call any subclass of Base, such as Group, it will call
436
+ # Base.connect() if these is no active LDAP connection. If your server allows
437
+ # anonymous binding, and you only want to access data in a read-only fashion, you
438
+ # won't need to call Base.connect. Here is a fully parameterized call:
439
+ #
440
+ # Base.connect(
441
+ # :host => 'ldap.example.com',
442
+ # :port => 389,
443
+ # :base => 'dc=example,dc=com',
444
+ # :bind_format => "uid=%s,ou=People,dc=example,dc=com",
445
+ # :logger => log4r_obj,
446
+ # :user => 'drewry',
447
+ # :password_block => Proc.new { 'password12345' },
448
+ # :allow_anonymous => false,
449
+ # :try_sasl => false
450
+ # )
451
+ #
452
+ # There are quite a few arguments, but luckily many of them have safe defaults:
453
+ # * :host defaults to @@host from configuration.rb waaay back at the setup.rb stage.@
454
+ # * :port defaults to @@port from configuration.rb as well
455
+ # * :base defaults to Base.base() from configuration.rb
456
+ # * :bind_format defaults @@bind_format from configuration.rb
457
+ # * :logger defaults to a Log4r object that prints fatal messages to stderr
458
+ # * :user defaults to ENV['user']
459
+ # * :password_block defaults to nil
460
+ # * :allow_anonymous defaults to true
461
+ # * :try_sasl defaults to false - see Advanced Topics for more on this one.
462
+ #
463
+ #
464
+ # Most of these are obvious, but I'll step through them for completeness:
465
+ # * :host defines the LDAP server hostname to connect to.
466
+ # * :port defines the LDAP server port to connect to.
467
+ # * :base specifies the LDAP search base to use with the prefixes defined in all
468
+ # subclasses.
469
+ # * :bind_format specifies what your server expects when attempting to bind with
470
+ # credentials.
471
+ # * :logger accepts a custom log4r object to integrate with any other logging
472
+ # your application uses.
473
+ # * :user gives the username to substitute into bind_format for binding with
474
+ # credentials
475
+ # * :password_block, if defined, give the Proc block for acquiring the password
476
+ # * :allow_anonymous determines whether anonymous binding is allowed if other
477
+ # bind methods fail
478
+ #
479
+ # Base.connect both connects and binds in one step. It follows roughly the following approach:
480
+ #
481
+ # * Connect to host:port.
482
+ # * Try TLS.
483
+ # * If that fails try SSL.
484
+ # * If that fails try no encryption.
485
+ #
486
+ # * If user and password_block, attempt to bind with credentials.
487
+ # * If that fails or no password_block and anonymous allowed, attempt to bind
488
+ # anonymously.
489
+ # * If that fails, error out.
490
+ #
491
+ # On connect, the configuration options passed in are stored in an internal class variable
492
+ # @@config which is used to cache the information without ditching the defaults passed in
493
+ # from configuration.rb
494
+ #
495
+ # ===== close
496
+ #
497
+ # Base.close discards the current LDAP connection.
498
+ #
499
+ # ===== connection
500
+ #
501
+ # Base.connection returns the raw LDAP connection object.
502
+ #
503
+ # === Exceptions
504
+ #
505
+ # There are a few custom exceptions used in Ruby/ActiveLDAP. They are detailed below.
506
+ #
507
+ # ==== AttributeEmpty
508
+ #
509
+ # This exception is raised when a required attribute is empty. It is only raised
510
+ # by #validate, and by proxy, #write.
511
+ #
512
+ # ==== DeleteError
513
+ #
514
+ # This exception is raised when #delete fails. It will include LDAP error
515
+ # information that was passed up during the error.
516
+ #
517
+ # ==== WriteError
518
+ #
519
+ # This exception is raised when there is a problem in #write updating or creating
520
+ # an LDAP entry. Often the error messages are cryptic. Looking at the server
521
+ # logs or doing an Ethereal[http://www.ethereal.com] dump of the connection will
522
+ # often provide better insight.
523
+ #
524
+ # ==== AuthenticationError
525
+ #
526
+ # This exception is raised during Base.connect if no valid authentication methods
527
+ # succeeded.
528
+ #
529
+ # ==== ConnectionError
530
+ #
531
+ # This exception is raised during Base.connect if no valid connection to the
532
+ # LDAP server could be created. Check you configuration.rb, Base.connect
533
+ # arguments, and network connectivity! Also check your LDAP server logs to see
534
+ # if it ever saw the request.
535
+ #
536
+ # === Others
537
+ #
538
+ # Other exceptions may be raised by the Ruby/LDAP module, or by other subsystems.
539
+ # If you get one of these exceptions and thing it should be wrapped, write me an
540
+ # email and let me know where it is and what you expected. For faster results,
541
+ # email a patch!
542
+ #
543
+ # === Putting it all together
544
+ #
545
+ # Now that all of the components of Ruby/ActiveLDAP have been covered, it's time
546
+ # to put it all together! The rest of this section will show the steps to setup
547
+ # example user and group management scripts for use with the LDAP tree described
548
+ # above.
549
+ #
550
+ # All of the scripts here are in the package's examples/ directory.
551
+ #
552
+ # ==== Setting up lib/
553
+ #
554
+ # In ldapadmin/lib/ create the file user.rb:
555
+ # cat <<EOF
556
+ # class User < ActiveLDAP::Base
557
+ # ldap_mapping :dnattr => 'uid', :prefix => 'ou=People', :classes => ['top', 'account', 'posixAccount']
558
+ # belongs_to :groups, :class_name => 'Group', :foreign_key => 'memberUid'
559
+ # end
560
+ # EOF
561
+ #
562
+ # In ldapadmin/lib/ create the file group.rb:
563
+ # cat <<EOF
564
+ # class Group < ActiveLDAP::Base
565
+ # ldap_mapping :classes => ['top', 'posixGroup'], :prefix => 'ou=Group'
566
+ # has_many :members, :class_name => "User", :local_key => "memberUid"
567
+ # belongs_to :primary_members, :class_name => 'User', :foreign_key => 'gidNumber', :local_key => 'gidNumber'
568
+ # end # Group
569
+ # EOF
570
+ #
571
+ # Now, we can write some small scripts to do simple management tasks.
572
+ #
573
+ # ==== Creating LDAP entries
574
+ #
575
+ # Now let's create a really dumb script for adding users - ldapadmin/useradd:
576
+ #
577
+ # #!/usr/bin/ruby -W0
578
+ #
579
+ # require 'activeldap'
580
+ # require 'lib/user'
581
+ # require 'lib/group'
582
+ # require 'password'
583
+ #
584
+ # (printf($stderr, "Usage:\n%s name cn uid\n", $0); exit 1) if ARGV.size != 3
585
+ #
586
+ # puts "Adding user #{ARGV[0]}"
587
+ # pwb = Proc.new {
588
+ # Password.get('Password: ')
589
+ # }
590
+ # ActiveLDAP::Base.connect(:password_block => pwb, :allow_anonymous => false)
591
+ # user = User.new(ARGV[0])
592
+ # user.objectClass = user.objectClass << 'posixAccount' << 'shadowAccount'
593
+ # user.cn = ARGV[1]
594
+ # user.uidNumber = ARGV[2]
595
+ # user.gidNumber = ARGV[2]
596
+ # user.homeDirectory = "/home/#{ARGV[0]}"
597
+ # user.write
598
+ # puts "User created!"
599
+ # exit 0
600
+ #
601
+ #
602
+ # ==== Managing LDAP entries
603
+ #
604
+ # Now let's create another dumb script for modifying users - ldapadmin/usermod:
605
+ #
606
+ # #!/usr/bin/ruby -W0
607
+ #
608
+ # require 'activeldap'
609
+ # require 'lib/user'
610
+ # require 'lib/group'
611
+ # require 'password'
612
+ #
613
+ # (printf($stderr, "Usage:\n%s name cn uid\n", $0); exit 1) if ARGV.size != 3
614
+ #
615
+ # puts "Changing user #{ARGV[0]}"
616
+ # pwb = Proc.new {
617
+ # Password.get('Password: ')
618
+ # }
619
+ # ActiveLDAP::Base.connect(:password_block => pwb, :allow_anonymous => false)
620
+ # user = User.new(ARGV[0])
621
+ # user.cn = ARGV[1]
622
+ # user.uidNumber = ARGV[2]
623
+ # user.gidNumber = ARGV[2]
624
+ # user.write
625
+ # puts "Modification successful!"
626
+ # exit 0
627
+ #
628
+ #
629
+ #
630
+ # ==== Removing LDAP entries
631
+ #
632
+ # And finally, a dumb script for removing user - ldapadmin/userdel:
633
+ #
634
+ #
635
+ # #!/usr/bin/ruby -W0
636
+ #
637
+ # require 'activeldap'
638
+ # require 'lib/user'
639
+ # require 'lib/group'
640
+ # require 'password'
641
+ #
642
+ # (printf($stderr, "Usage:\n%s name\n", $0); exit 1) if ARGV.size != 1
643
+ #
644
+ # puts "Changing user #{ARGV[0]}"
645
+ # pwb = Proc.new {
646
+ # Password.get('Password: ')
647
+ # }
648
+ # ActiveLDAP::Base.connect(:password_block => pwb, :allow_anonymous => false)
649
+ # user = User.new(ARGV[0])
650
+ # user.delete
651
+ # puts "User has been delete"
652
+ # exit 0
653
+ #
654
+ # === Advanced Topics
655
+ #
656
+ # Below are some situation tips and tricks to get the most out of Ruby/ActiveLDAP.
657
+ #
658
+ #
659
+ # ==== Binary data and other subtypes
660
+ #
661
+ # Sometimes, you may want to store attributes with language specifiers, or
662
+ # perhaps in binary form. This is (finally!) fully supported. To do so,
663
+ # follow the examples below:
664
+ #
665
+ # irb> user = User.new('drewry')
666
+ # => ...
667
+ # # This adds a cn entry in lang-en and whatever the server default is.
668
+ # irb> user.cn = [ 'wad', {'lang-en' => ['wad', 'foo']} ]
669
+ # => ...
670
+ # irb> user.cn
671
+ # => ["wad", {"lang-en-us" => ["wad", "Will Drewry"]}]
672
+ # # Now let's add a binary X.509 certificate (assume objectClass is correct)
673
+ # irb> user.userCertificate = File.read('example.der')
674
+ # => ...
675
+ # irb> user.write
676
+ #
677
+ # So that's a lot to take in. Here's what is going on. I just set the LDAP
678
+ # object's cn to "wad" and cn:lang-en-us to ["wad", "Will Drewry"].
679
+ # Anytime a LDAP subtype is required, you must encapsulate the data in a Hash.
680
+ #
681
+ # But wait a minute, I just read in a binary certificate without wrapping it up.
682
+ # So any binary attribute will automagically get wrapped in {'binary' => value}
683
+ # if you don't do it. This keeps your #writes from breaking, and my code from
684
+ # crying. For correctness, I could have easily done the following:
685
+ #
686
+ # irb> user.userCertificate = {'binary' => File.read('example.der')}
687
+ #
688
+ # Really, it's up to you. The only subtypes defined in LDAPv3 are lang-* and
689
+ # binary. These can be nested through:
690
+ #
691
+ # irb> user.cn = [{'lang-JP-jp' => {'binary' => 'somejp'}}]
692
+ #
693
+ # As I understand it, OpenLDAP does not support nested subtypes, but some
694
+ # documentation I've read suggests that Netscape's LDAP server does. I only
695
+ # have access to OpenLDAP. If anyone tests thisout, please let me know how it
696
+ # goes!.
697
+ #
698
+ #
699
+ # And that pretty much wraps up this section.
700
+ #
701
+ # ==== Further integration with your environment aka namespacing
702
+ #
703
+ # If you want this to cleanly integrate into your system-wide Ruby include path,
704
+ # you should put your extension classes inside a custom module.
705
+ #
706
+ #
707
+ # Example:
708
+ #
709
+ # ./myldap.rb:
710
+ # require 'activeldap'
711
+ # require 'myldap/user'
712
+ # require 'myldap/group'
713
+ # module MyLDAP
714
+ # end
715
+ #
716
+ # ./myldap/user.rb:
717
+ # module MyLDAP
718
+ # class User < ActiveLDAP::Base
719
+ # ldap_mapping :dnattr => 'uid', :prefix => 'ou=People', :classes => ['top', 'account', 'posixAccount']
720
+ # belongs_to :groups, :class_name => 'MyLDAP::Group', :foreign_key => 'memberUid'
721
+ # end
722
+ # end
723
+ #
724
+ # ./myldap/group.rb:
725
+ # module MyLDAP
726
+ # class Group < ActiveLDAP::Base
727
+ # ldap_mapping :classes => ['top', 'posixGroup'], :prefix => 'ou=Group'
728
+ # has_many :members, :class_name => 'MyLDAP::User', :local_key => 'memberUid'
729
+ # belongs_to :primary_members, :class_name => 'MyLDAP::User', :foreign_key => 'gidNumber', :local_key => 'gidNumber'
730
+ # end
731
+ # end
732
+ #
733
+ # Now in your local applications, you can call
734
+ #
735
+ # require 'myldap'
736
+ #
737
+ # MyLDAP::Group.new('foo')
738
+ # ...
739
+ #
740
+ # and everything should work well.
741
+ #
742
+ #
743
+ # ==== Array results for everything
744
+ #
745
+ # Even though Ruby/ActiveLDAP tries to be convenient by returning Arrays only
746
+ # when an LDAP object attribute has multiple values, sometimes this can be a
747
+ # programmatic nightmare. If you would like to force all returned values to
748
+ # come in Array form, just query like so:
749
+ #
750
+ # irb> user = User.new('drewry')
751
+ # => ...
752
+ # irb> user.cn(true)
753
+ # => ["Will Drewry"]
754
+ #
755
+ # That's it. Now you can ALWAYS get Arrays back without having to write your own
756
+ # wrappers.
757
+ #
758
+ # ==== Dynamic attribute crawling
759
+ #
760
+ # If you use tab completion in irb, you'll notice that you can't tab complete the
761
+ # dynamic attributes available for each object. You can still see what's available with the method call
762
+ # #attributes.
763
+ #
764
+ # irb> d = Group.new('develop')
765
+ # => ...
766
+ # irb> d.attributes
767
+ # => ["gidNumber", "cn", "memberUid", "commonName", "description", "userPassword", "objectClass"]
768
+ #
769
+ #
770
+ # ==== Advanced LDAP queries
771
+ #
772
+ # With the addition of Base.search, you can do arbitrary queries against LDAP
773
+ # without needed to understand how to use Ruby/LDAP.
774
+ #
775
+ # If that's still not enough, you can access the
776
+ # Ruby/LDAP connection object using the class method Base.connection. You can
777
+ # do all of your LDAP specific calls here and then continue about your normal
778
+ # Ruby/ActiveLDAP business afterward.
779
+ #
780
+ #
781
+ # ==== Juggling multiple LDAP connections
782
+ #
783
+ # In the same vein as the last tip, you can use multiple LDAP connections by
784
+ # saving old connections and then resetting them as follows:
785
+ #
786
+ # irb> Base.connect()
787
+ # => ...
788
+ # irb> anon_conn = Base.connection
789
+ # => ...
790
+ # irb> Base.connect(password_block => {'mypass'})
791
+ # => ...
792
+ # irb> auth_conn = Base.connection
793
+ # => ...
794
+ # irb> Base.connection = anon_conn
795
+ # ...
796
+ #
797
+ # This can be useful for doing authentication tests and other such tricks.
798
+ #
799
+ # ==== :try_sasl
800
+ #
801
+ # If you have the Ruby/LDAP package with the SASL/GSSAPI patch from Ian
802
+ # MacDonald's web site, you can use Kerberos to bind to your LDAP server. By
803
+ # default, :try_sasl is false.
804
+ #
805
+ # Also note that you must be using OpenLDAP 2.1.29 or higher to use SASL/GSSAPI
806
+ # due to some bugs in older versions of OpenLDAP.
807
+ #
808
+ # ==== Don't be afraid! [Internals]
809
+ #
810
+ # Don't be afraid to add more methods to the extensions classes and to
811
+ # experiment. That's exactly how I ended up with this package. If you come up
812
+ # with something cool, please share it!
813
+ #
814
+ # The internal structure of ActiveLDAP::Base, and thus all its subclasses, is
815
+ # still in flux. I've tried to minimize the changes to the overall API, but
816
+ # the internals are still rough around the edges.
817
+ #
818
+ # ===== Where's ldap_mapping data stored? How can I get to it?
819
+ #
820
+ # When you call ldap_mapping, it overwrites several class methods inherited
821
+ # from Base:
822
+ # * Base.base()
823
+ # * Base.required_classes()
824
+ # * Base.dnattr()
825
+ # You can access these from custom class methods by calling MyClass.base(),
826
+ # or whatever. There are predefined instance methods for getting to these
827
+ # from any new instance methods you define:
828
+ # * Base#base()
829
+ # * Base#required_classes()
830
+ # * Base#dnattr()
831
+ #
832
+ # ===== What else?
833
+ #
834
+ # Well if you want to use the LDAP connection for anything, I'd suggest still
835
+ # calling Base.connection to get it. There really aren't many other internals
836
+ # that need to be worried about. You could rebind with Base.do_bind, and get
837
+ # the LDAP schema with Base.schema.
838
+ #
839
+ # The only other useful tricks are dereferencing and accessing the stored
840
+ # data. Since LDAP attributes can have multiple names, e.g. cn or commonName,
841
+ # any methods you write might need to figure it out. I'd suggest just
842
+ # calling self.[attribname] to get the value, but if that's not good enough,
843
+ # you can call look up the stored name in the @attr_method Array as follows:
844
+ # irb> @attr_method['commonName']
845
+ # => 'cn'
846
+ #
847
+ # This tells you the name the attribute is stored in behind the scenes (@data).
848
+ # Again, self.[attribname] should be enough for most extensions, but if not,
849
+ # it's probably safe to dabble here.
850
+ #
851
+ # Also, if you like to look up all aliases for an attribute, you can call the
852
+ # following:
853
+ #
854
+ # irb> attribute_aliases('cn')
855
+ # => ['cn','commonName']
856
+ #
857
+ # This is discovered automagically from the LDAP server's schema.
858
+ #
859
+ # == Limitations
860
+ #
861
+ # === Speed
862
+ #
863
+ # Currently, Ruby/ActiveLDAP could be faster. I have some recursive type
864
+ # checking going on which slows object creation down, and I'm sure there
865
+ # are many, many other places optimizations can be done. Feel free
866
+ # to send patches, or just hang in there until I can optimize away the
867
+ # slowness.
868
+ #
869
+ # == Feedback
870
+ #
871
+ # Any and all feedback and patches are welcome. I am very excited about this
872
+ # package, and I'd like to see it prove helpful to more people than just myself.
873
+ #
874
+
875
+
876
+
877
+ require 'activeldap/base'
878
+ require 'activeldap/associations'
879
+ require 'activeldap/configuration'
880
+ require 'activeldap/schema2'
881
+
882
+ module ActiveLDAP
883
+ VERSION = "0.4.1"
884
+ end
885
+
886
+ ActiveLDAP::Base.class_eval do
887
+ include ActiveLDAP::Configuration
888
+ include ActiveLDAP::Associations
889
+ end