powerhome-activeldap 3.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +6 -0
- data/COPYING +340 -0
- data/Gemfile +12 -0
- data/LICENSE +59 -0
- data/README.textile +140 -0
- data/TODO +32 -0
- data/benchmark/README.md +64 -0
- data/benchmark/bench-backend.rb +247 -0
- data/benchmark/bench-instantiate.rb +98 -0
- data/benchmark/config.yaml.sample +5 -0
- data/doc/text/development.textile +54 -0
- data/doc/text/news.textile +811 -0
- data/doc/text/rails.textile +144 -0
- data/doc/text/tutorial.textile +1010 -0
- data/examples/config.yaml.example +5 -0
- data/examples/example.der +0 -0
- data/examples/example.jpg +0 -0
- data/examples/groupadd +41 -0
- data/examples/groupdel +35 -0
- data/examples/groupls +49 -0
- data/examples/groupmod +42 -0
- data/examples/lpasswd +55 -0
- data/examples/objects/group.rb +13 -0
- data/examples/objects/ou.rb +4 -0
- data/examples/objects/user.rb +20 -0
- data/examples/ouadd +38 -0
- data/examples/useradd +45 -0
- data/examples/useradd-binary +53 -0
- data/examples/userdel +34 -0
- data/examples/userls +50 -0
- data/examples/usermod +42 -0
- data/examples/usermod-binary-add +50 -0
- data/examples/usermod-binary-add-time +54 -0
- data/examples/usermod-binary-del +48 -0
- data/examples/usermod-lang-add +43 -0
- data/lib/active_ldap.rb +85 -0
- data/lib/active_ldap/action_controller/ldap_benchmarking.rb +55 -0
- data/lib/active_ldap/acts/tree.rb +78 -0
- data/lib/active_ldap/adapter/base.rb +707 -0
- data/lib/active_ldap/adapter/jndi.rb +184 -0
- data/lib/active_ldap/adapter/jndi_connection.rb +185 -0
- data/lib/active_ldap/adapter/ldap.rb +290 -0
- data/lib/active_ldap/adapter/ldap_ext.rb +105 -0
- data/lib/active_ldap/adapter/net_ldap.rb +309 -0
- data/lib/active_ldap/adapter/net_ldap_ext.rb +23 -0
- data/lib/active_ldap/association/belongs_to.rb +47 -0
- data/lib/active_ldap/association/belongs_to_many.rb +58 -0
- data/lib/active_ldap/association/children.rb +21 -0
- data/lib/active_ldap/association/collection.rb +105 -0
- data/lib/active_ldap/association/has_many.rb +31 -0
- data/lib/active_ldap/association/has_many_utils.rb +44 -0
- data/lib/active_ldap/association/has_many_wrap.rb +75 -0
- data/lib/active_ldap/association/proxy.rb +107 -0
- data/lib/active_ldap/associations.rb +205 -0
- data/lib/active_ldap/attribute_methods.rb +23 -0
- data/lib/active_ldap/attribute_methods/before_type_cast.rb +24 -0
- data/lib/active_ldap/attribute_methods/dirty.rb +43 -0
- data/lib/active_ldap/attribute_methods/query.rb +31 -0
- data/lib/active_ldap/attribute_methods/read.rb +44 -0
- data/lib/active_ldap/attribute_methods/write.rb +38 -0
- data/lib/active_ldap/attributes.rb +176 -0
- data/lib/active_ldap/base.rb +1410 -0
- data/lib/active_ldap/callbacks.rb +71 -0
- data/lib/active_ldap/command.rb +49 -0
- data/lib/active_ldap/compatible.rb +44 -0
- data/lib/active_ldap/configuration.rb +147 -0
- data/lib/active_ldap/connection.rb +299 -0
- data/lib/active_ldap/distinguished_name.rb +291 -0
- data/lib/active_ldap/entry_attribute.rb +78 -0
- data/lib/active_ldap/escape.rb +12 -0
- data/lib/active_ldap/get_text.rb +20 -0
- data/lib/active_ldap/get_text/parser.rb +161 -0
- data/lib/active_ldap/helper.rb +92 -0
- data/lib/active_ldap/human_readable.rb +133 -0
- data/lib/active_ldap/ldap_error.rb +74 -0
- data/lib/active_ldap/ldif.rb +930 -0
- data/lib/active_ldap/log_subscriber.rb +50 -0
- data/lib/active_ldap/object_class.rb +95 -0
- data/lib/active_ldap/operations.rb +624 -0
- data/lib/active_ldap/persistence.rb +100 -0
- data/lib/active_ldap/populate.rb +53 -0
- data/lib/active_ldap/railtie.rb +43 -0
- data/lib/active_ldap/railties/controller_runtime.rb +48 -0
- data/lib/active_ldap/schema.rb +701 -0
- data/lib/active_ldap/schema/syntaxes.rb +422 -0
- data/lib/active_ldap/timeout.rb +75 -0
- data/lib/active_ldap/timeout_stub.rb +17 -0
- data/lib/active_ldap/user_password.rb +99 -0
- data/lib/active_ldap/validations.rb +200 -0
- data/lib/active_ldap/version.rb +3 -0
- data/lib/active_ldap/xml.rb +139 -0
- data/lib/rails/generators/active_ldap/model/USAGE +18 -0
- data/lib/rails/generators/active_ldap/model/model_generator.rb +47 -0
- data/lib/rails/generators/active_ldap/model/templates/model_active_ldap.rb +3 -0
- data/lib/rails/generators/active_ldap/scaffold/scaffold_generator.rb +14 -0
- data/lib/rails/generators/active_ldap/scaffold/templates/ldap.yml +19 -0
- data/po/en/active-ldap.po +4029 -0
- data/po/ja/active-ldap.po +4060 -0
- data/test/add-phonetic-attribute-options-to-slapd.ldif +10 -0
- data/test/al-test-utils.rb +428 -0
- data/test/command.rb +111 -0
- data/test/config.yaml.sample +6 -0
- data/test/fixtures/lower_case_object_class_schema.rb +802 -0
- data/test/run-test.rb +34 -0
- data/test/test_acts_as_tree.rb +60 -0
- data/test/test_adapter.rb +121 -0
- data/test/test_associations.rb +701 -0
- data/test/test_attributes.rb +117 -0
- data/test/test_base.rb +1214 -0
- data/test/test_base_per_instance.rb +61 -0
- data/test/test_bind.rb +62 -0
- data/test/test_callback.rb +31 -0
- data/test/test_configuration.rb +40 -0
- data/test/test_connection.rb +82 -0
- data/test/test_connection_per_class.rb +112 -0
- data/test/test_connection_per_dn.rb +112 -0
- data/test/test_dirty.rb +98 -0
- data/test/test_dn.rb +172 -0
- data/test/test_find.rb +176 -0
- data/test/test_groupadd.rb +50 -0
- data/test/test_groupdel.rb +46 -0
- data/test/test_groupls.rb +107 -0
- data/test/test_groupmod.rb +51 -0
- data/test/test_ldif.rb +1890 -0
- data/test/test_load.rb +133 -0
- data/test/test_lpasswd.rb +75 -0
- data/test/test_object_class.rb +74 -0
- data/test/test_persistence.rb +131 -0
- data/test/test_reflection.rb +175 -0
- data/test/test_schema.rb +559 -0
- data/test/test_syntax.rb +444 -0
- data/test/test_user.rb +217 -0
- data/test/test_user_password.rb +108 -0
- data/test/test_useradd-binary.rb +62 -0
- data/test/test_useradd.rb +57 -0
- data/test/test_userdel.rb +48 -0
- data/test/test_userls.rb +91 -0
- data/test/test_usermod-binary-add-time.rb +65 -0
- data/test/test_usermod-binary-add.rb +64 -0
- data/test/test_usermod-binary-del.rb +66 -0
- data/test/test_usermod-lang-add.rb +59 -0
- data/test/test_usermod.rb +58 -0
- data/test/test_validation.rb +274 -0
- 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
|
+
|