powerhome-activeldap 3.2.3

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