activeldap 1.2.4 → 3.1.0

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