ruby-activeldap-debug 0.5.5

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