ruby-activeldap 0.4.1

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,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