powerhome-activeldap 3.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+