ruby-activeldap 0.7.4 → 0.8.0
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.
- data/CHANGES +375 -0
- data/COPYING +340 -0
- data/LICENSE +58 -0
- data/Manifest.txt +33 -0
- data/README +63 -0
- data/Rakefile +37 -0
- data/TODO +31 -0
- data/benchmark/bench-al.rb +152 -0
- data/lib/{activeldap.rb → active_ldap.rb} +280 -263
- data/lib/active_ldap/adaptor/base.rb +29 -0
- data/lib/active_ldap/adaptor/ldap.rb +466 -0
- data/lib/active_ldap/association/belongs_to.rb +38 -0
- data/lib/active_ldap/association/belongs_to_many.rb +40 -0
- data/lib/active_ldap/association/collection.rb +80 -0
- data/lib/active_ldap/association/has_many.rb +48 -0
- data/lib/active_ldap/association/has_many_wrap.rb +56 -0
- data/lib/active_ldap/association/proxy.rb +89 -0
- data/lib/active_ldap/associations.rb +162 -0
- data/lib/active_ldap/attributes.rb +199 -0
- data/lib/active_ldap/base.rb +1343 -0
- data/lib/active_ldap/callbacks.rb +19 -0
- data/lib/active_ldap/command.rb +46 -0
- data/lib/active_ldap/configuration.rb +96 -0
- data/lib/active_ldap/connection.rb +137 -0
- data/lib/{activeldap → active_ldap}/ldap.rb +1 -1
- data/lib/active_ldap/object_class.rb +70 -0
- data/lib/active_ldap/schema.rb +258 -0
- data/lib/{activeldap → active_ldap}/timeout.rb +0 -0
- data/lib/{activeldap → active_ldap}/timeout_stub.rb +0 -0
- data/lib/active_ldap/user_password.rb +92 -0
- data/lib/active_ldap/validations.rb +78 -0
- data/rails/plugin/active_ldap/README +54 -0
- data/rails/plugin/active_ldap/init.rb +6 -0
- data/test/TODO +2 -0
- data/test/al-test-utils.rb +337 -0
- data/test/command.rb +62 -0
- data/test/config.yaml +8 -0
- data/test/config.yaml.sample +6 -0
- data/test/run-test.rb +17 -0
- data/test/test-unit-ext.rb +2 -0
- data/test/test_associations.rb +334 -0
- data/test/test_attributes.rb +71 -0
- data/test/test_base.rb +345 -0
- data/test/test_base_per_instance.rb +32 -0
- data/test/test_bind.rb +53 -0
- data/test/test_callback.rb +35 -0
- data/test/test_connection.rb +38 -0
- data/test/test_connection_per_class.rb +50 -0
- data/test/test_find.rb +36 -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_lpasswd.rb +75 -0
- data/test/test_object_class.rb +32 -0
- data/test/test_reflection.rb +173 -0
- data/test/test_schema.rb +166 -0
- data/test/test_user.rb +209 -0
- data/test/test_user_password.rb +93 -0
- data/test/test_useradd-binary.rb +59 -0
- data/test/test_useradd.rb +55 -0
- data/test/test_userdel.rb +48 -0
- data/test/test_userls.rb +86 -0
- data/test/test_usermod-binary-add-time.rb +62 -0
- data/test/test_usermod-binary-add.rb +61 -0
- data/test/test_usermod-binary-del.rb +64 -0
- data/test/test_usermod-lang-add.rb +57 -0
- data/test/test_usermod.rb +56 -0
- data/test/test_validation.rb +38 -0
- metadata +94 -21
- data/lib/activeldap/associations.rb +0 -170
- data/lib/activeldap/base.rb +0 -1456
- data/lib/activeldap/configuration.rb +0 -59
- data/lib/activeldap/schema2.rb +0 -217
data/LICENSE
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.co.jp>.
|
2
|
+
You can redistribute it and/or modify it under either the terms of the GPL
|
3
|
+
(see COPYING file), or the conditions below:
|
4
|
+
|
5
|
+
1. You may make and give away verbatim copies of the source form of the
|
6
|
+
software without restriction, provided that you duplicate all of the
|
7
|
+
original copyright notices and associated disclaimers.
|
8
|
+
|
9
|
+
2. You may modify your copy of the software in any way, provided that
|
10
|
+
you do at least ONE of the following:
|
11
|
+
|
12
|
+
a) place your modifications in the Public Domain or otherwise
|
13
|
+
make them Freely Available, such as by posting said
|
14
|
+
modifications to Usenet or an equivalent medium, or by allowing
|
15
|
+
the author to include your modifications in the software.
|
16
|
+
|
17
|
+
b) use the modified software only within your corporation or
|
18
|
+
organization.
|
19
|
+
|
20
|
+
c) rename any non-standard executables so the names do not conflict
|
21
|
+
with standard executables, which must also be provided.
|
22
|
+
|
23
|
+
d) make other distribution arrangements with the author.
|
24
|
+
|
25
|
+
3. You may distribute the software in object code or executable
|
26
|
+
form, provided that you do at least ONE of the following:
|
27
|
+
|
28
|
+
a) distribute the executables and library files of the software,
|
29
|
+
together with instructions (in the manual page or equivalent)
|
30
|
+
on where to get the original distribution.
|
31
|
+
|
32
|
+
b) accompany the distribution with the machine-readable source of
|
33
|
+
the software.
|
34
|
+
|
35
|
+
c) give non-standard executables non-standard names, with
|
36
|
+
instructions on where to get the original software distribution.
|
37
|
+
|
38
|
+
d) make other distribution arrangements with the author.
|
39
|
+
|
40
|
+
4. You may modify and include the part of the software into any other
|
41
|
+
software (possibly commercial). But some files in the distribution
|
42
|
+
are not written by the author, so that they are not under this terms.
|
43
|
+
|
44
|
+
They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some
|
45
|
+
files under the ./missing directory. See each file for the copying
|
46
|
+
condition.
|
47
|
+
|
48
|
+
5. The scripts and library files supplied as input to or produced as
|
49
|
+
output from the software do not automatically fall under the
|
50
|
+
copyright of the software, but belong to whomever generated them,
|
51
|
+
and may be sold commercially, and may be aggregated with this
|
52
|
+
software.
|
53
|
+
|
54
|
+
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
55
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
56
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
57
|
+
PURPOSE.
|
58
|
+
|
data/Manifest.txt
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
Rakefile
|
2
|
+
LICENSE
|
3
|
+
TODO
|
4
|
+
COPYING
|
5
|
+
CHANGES
|
6
|
+
README
|
7
|
+
Manifest.txt
|
8
|
+
lib/active_ldap/adaptor/ldap.rb
|
9
|
+
lib/active_ldap/adaptor/base.rb
|
10
|
+
lib/active_ldap/association/has_many_wrap.rb
|
11
|
+
lib/active_ldap/association/has_many.rb
|
12
|
+
lib/active_ldap/association/proxy.rb
|
13
|
+
lib/active_ldap/association/belongs_to_many.rb
|
14
|
+
lib/active_ldap/association/collection.rb
|
15
|
+
lib/active_ldap/association/belongs_to.rb
|
16
|
+
lib/active_ldap/validations.rb
|
17
|
+
lib/active_ldap/command.rb
|
18
|
+
lib/active_ldap/callbacks.rb
|
19
|
+
lib/active_ldap/ldap.rb
|
20
|
+
lib/active_ldap/timeout_stub.rb
|
21
|
+
lib/active_ldap/timeout.rb
|
22
|
+
lib/active_ldap/attributes.rb
|
23
|
+
lib/active_ldap/object_class.rb
|
24
|
+
lib/active_ldap/connection.rb
|
25
|
+
lib/active_ldap/associations.rb
|
26
|
+
lib/active_ldap/user_password.rb
|
27
|
+
lib/active_ldap/schema.rb
|
28
|
+
lib/active_ldap/configuration.rb
|
29
|
+
lib/active_ldap/base.rb
|
30
|
+
lib/active_ldap.rb
|
31
|
+
benchmark/bench-al.rb
|
32
|
+
rails/plugin/active_ldap/init.rb
|
33
|
+
rails/plugin/active_ldap/README
|
data/README
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
Ruby/ActiveLdap -- ruby library for object-oriented LDAP interction
|
2
|
+
Copyright (C) 2004-2006 Will Drewry <will@alum.bu.edu>, Kouhei Sutou <kou@cozmixng.org>
|
3
|
+
Contributors:
|
4
|
+
* Dick Davies <rasputnik@hellooperator.net>
|
5
|
+
* Nathan Kinder <quicksilver02@mac.com>
|
6
|
+
* Patrick Cole <pac@independent.com.au>
|
7
|
+
* Google Inc.
|
8
|
+
|
9
|
+
DESCRIPTION
|
10
|
+
'Ruby/ActiveLdap' is a ruby extension library which provides a clean objected
|
11
|
+
oriented interface to the Ruby/LDAP[0] library. It was inspired by
|
12
|
+
ActivRecord[2]. This is not nearly as clean or as flexible as ActiveRecord, but
|
13
|
+
it is still trivial to define new objects and manipulate them with minimal
|
14
|
+
difficulty.
|
15
|
+
|
16
|
+
For example and usage - read the rdoc in doc/ from lib/activeldap.rb.
|
17
|
+
It is also available on the web at:
|
18
|
+
|
19
|
+
http://static.dataspill.org/projects/libraries/ruby/activeldap/rdoc/
|
20
|
+
|
21
|
+
PREREQUISITES
|
22
|
+
|
23
|
+
* Ruby 1.8.x [1]
|
24
|
+
* Ruby/LDAP [0]
|
25
|
+
* Log4r [4]
|
26
|
+
* ActiveRecord [2]
|
27
|
+
* (Optional) Ruby/LDAP+GSSAPI [3]
|
28
|
+
* An LDAP server compatible with Ruby/LDAP: OpenLDAP, etc
|
29
|
+
|
30
|
+
|
31
|
+
NOTES
|
32
|
+
|
33
|
+
* Only GSSAPI SASL support exists due to Ruby/LDAP limitations
|
34
|
+
* The API is subject to change as this package slowly approaches 1.0.0
|
35
|
+
|
36
|
+
|
37
|
+
INSTALL
|
38
|
+
|
39
|
+
- Edit active_ldap/configuration.rb with your LDAP preferences
|
40
|
+
- Run -
|
41
|
+
sudo rake install
|
42
|
+
|
43
|
+
|
44
|
+
LICENCE
|
45
|
+
|
46
|
+
This program is free software; you can redistribute it and/or modify
|
47
|
+
it under the terms of the GNU General Public License as published by
|
48
|
+
the Free Software Foundation; either version 2, or (at your option)
|
49
|
+
any later version.
|
50
|
+
|
51
|
+
Please see the file LICENSE for the terms of the licence.
|
52
|
+
|
53
|
+
|
54
|
+
REFERENCES
|
55
|
+
|
56
|
+
[0] - http://ruby-ldap.sourceforge.net
|
57
|
+
[1] - http://www.ruby-lang.org
|
58
|
+
[2] - http://activerecord.rubyonrails.org
|
59
|
+
[3] - http://caliban.org/files/redhat/RPMS/i386/ruby-ldap-0.8.2-4.i386.rpm
|
60
|
+
[4] - http://log4r.sourceforge.net/
|
61
|
+
|
62
|
+
|
63
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
$:<<'./lib'
|
6
|
+
require 'active_ldap'
|
7
|
+
|
8
|
+
Hoe.new('ruby-activeldap', ActiveLdap::VERSION) do |project|
|
9
|
+
project.rubyforge_name = 'ruby-activeldap'
|
10
|
+
project.author = ['Will Drewry', 'Kouhei Sutou']
|
11
|
+
project.email = ['will@alum.bu.edu', 'kou@cozmixng.org']
|
12
|
+
project.summary = 'Ruby/ActiveLdap is a object-oriented API to LDAP'
|
13
|
+
project.url = 'http://rubyforge.org/projects/ruby-activeldap/'
|
14
|
+
project.test_globs = ['test/**']
|
15
|
+
project.changes = project.paragraphs_of('CHANGES', 0..1).join("\n\n")
|
16
|
+
project.extra_deps = [['log4r','>= 1.0.4'], 'activerecord']
|
17
|
+
project.spec_extras = {
|
18
|
+
:requirements => ['ruby-ldap >= 0.8.2', '(Open)LDAP server'],
|
19
|
+
:autorequire => 'active_ldap'
|
20
|
+
}
|
21
|
+
project.description = String.new(<<-EOF)
|
22
|
+
'Ruby/ActiveLdap' is a ruby extension library which provides a clean
|
23
|
+
objected oriented interface to the Ruby/LDAP library. It was inspired
|
24
|
+
by ActiveRecord. This is not nearly as clean or as flexible as
|
25
|
+
ActiveRecord, but it is still trivial to define new objects and manipulate
|
26
|
+
them with minimal difficulty.
|
27
|
+
EOF
|
28
|
+
end
|
29
|
+
|
30
|
+
desc 'Tag the repository for release.'
|
31
|
+
task :tag do
|
32
|
+
system "svn copy -m 'New release tag' https://ruby-activeldap.googlecode.com/svn/trunk https://ruby-activeldap.googlecode.com/svn/tags/r#{ActiveLdap::VERSION}"
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
# vim: syntax=Ruby
|
data/TODO
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
- Fix case sensitivity in object classes
|
2
|
+
- Add result pagination via LDAP::Controls
|
3
|
+
- serialize & serialized_attributes
|
4
|
+
- schema mgmt - how does AR handle it?
|
5
|
+
- columns() -- ?
|
6
|
+
http://api.rubyonrails.com/classes/ActiveRecord/Base.html#M000865
|
7
|
+
- provide full documentation for new API.
|
8
|
+
- handle all exception raised from Ruby/LDAP and wrap as
|
9
|
+
ActiveLdap exception. I think we need to develop an
|
10
|
+
application using ActiveLdap.
|
11
|
+
- support Reloadable::Subclasses. I think we need to
|
12
|
+
develop a Rails + ActiveLdap application to improve
|
13
|
+
Rails support.
|
14
|
+
- support Ruby/GetText.
|
15
|
+
- support Net::LDAP as LDAP backend after Net::LDAP
|
16
|
+
supports START_TLS. (I made a patch and submitted to the
|
17
|
+
bug tracker of Net::LDAP)
|
18
|
+
- Add locking around Timeout.alarm() to ensure a multithreaded ruby
|
19
|
+
app doesn't hit any race conditions
|
20
|
+
- Add AR matching exceptions:
|
21
|
+
* ActiveRecordError -- ActiveLdapError as base
|
22
|
+
* AssociationTypeMismatch
|
23
|
+
* SerializationTypeMismatch
|
24
|
+
* ConnectionNotEstablished
|
25
|
+
* RecordNotFound
|
26
|
+
* LdapActionInvalid - like StatementInvalid
|
27
|
+
* MultiparameterAssignmentErrors
|
28
|
+
* AttributeAssignmentError
|
29
|
+
* RecordNotSaved
|
30
|
+
|
31
|
+
|
@@ -0,0 +1,152 @@
|
|
1
|
+
base = File.dirname(__FILE__)
|
2
|
+
$LOAD_PATH.unshift(File.expand_path(base))
|
3
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(base, "..", "lib")))
|
4
|
+
|
5
|
+
require "active_ldap"
|
6
|
+
require "benchmark"
|
7
|
+
|
8
|
+
LDAP_SERVER = "127.0.0.1"
|
9
|
+
LDAP_PORT = 389
|
10
|
+
LDAP_BASE = ENV["LDAP_BASE"] || "dc=localdomain"
|
11
|
+
LDAP_PREFIX = "ou=People"
|
12
|
+
LDAP_USER = nil
|
13
|
+
LDAP_PASSWORD = nil
|
14
|
+
|
15
|
+
class ALUser < ActiveLdap::Base
|
16
|
+
ldap_mapping :dn_attribute => 'uid', :prefix => LDAP_PREFIX,
|
17
|
+
:classes => ['posixAccount', 'person']
|
18
|
+
end
|
19
|
+
|
20
|
+
# === search_al
|
21
|
+
#
|
22
|
+
def search_al
|
23
|
+
count = 0
|
24
|
+
ALUser.find(:all).each do |e|
|
25
|
+
count += 1
|
26
|
+
end
|
27
|
+
return count
|
28
|
+
end # -- search_al
|
29
|
+
|
30
|
+
def search_al_without_object_creation
|
31
|
+
count = 0
|
32
|
+
ALUser.search.each do |e|
|
33
|
+
count += 1
|
34
|
+
end
|
35
|
+
return count
|
36
|
+
end
|
37
|
+
|
38
|
+
# === search_ldap
|
39
|
+
#
|
40
|
+
def search_ldap(conn)
|
41
|
+
count = 0
|
42
|
+
conn.search("#{LDAP_PREFIX},#{LDAP_BASE}",
|
43
|
+
LDAP::LDAP_SCOPE_SUBTREE,
|
44
|
+
"(uid=*)") do |e|
|
45
|
+
count += 1
|
46
|
+
end
|
47
|
+
count
|
48
|
+
end # -- search_ldap
|
49
|
+
|
50
|
+
def populate_base
|
51
|
+
suffixes = []
|
52
|
+
ActiveLdap::Base.base.split(/,/).reverse_each do |suffix|
|
53
|
+
prefix = suffixes.join(",")
|
54
|
+
suffixes.unshift(suffix)
|
55
|
+
name, value = suffix.split(/=/, 2)
|
56
|
+
next unless name == "dc"
|
57
|
+
dc_class = Class.new(ActiveLdap::Base)
|
58
|
+
dc_class.ldap_mapping :dnattr => "dc",
|
59
|
+
:prefix => "",
|
60
|
+
:scope => :base,
|
61
|
+
:classes => ["top", "dcObject", "organization"]
|
62
|
+
dc_class.instance_variable_set("@base", prefix)
|
63
|
+
next if dc_class.exists?(value, :prefix => "dc=#{value}")
|
64
|
+
dc = dc_class.new(value)
|
65
|
+
dc.o = dc.dc
|
66
|
+
dc.save
|
67
|
+
end
|
68
|
+
|
69
|
+
if ActiveLdap::Base.search.empty?
|
70
|
+
raise "Can't populate #{ActiveLdap::Base.base}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def populate_users
|
75
|
+
ou_class = Class.new(ActiveLdap::Base)
|
76
|
+
ou_class.ldap_mapping :dnattr => "ou",
|
77
|
+
:prefix => "",
|
78
|
+
:classes => ["top", "organizationalUnit"]
|
79
|
+
ou_class.new(LDAP_PREFIX.split(/=/)[1]).save!
|
80
|
+
|
81
|
+
100.times do |i|
|
82
|
+
name = i.to_s
|
83
|
+
user = ALUser.new(name)
|
84
|
+
user.uid_number = 100000 + i
|
85
|
+
user.gid_number = 100000 + i
|
86
|
+
user.cn = name
|
87
|
+
user.sn = name
|
88
|
+
user.home_directory = "/nonexistent"
|
89
|
+
user.save!
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def populate
|
94
|
+
populate_base
|
95
|
+
populate_users
|
96
|
+
end
|
97
|
+
|
98
|
+
# === main(argv)
|
99
|
+
#
|
100
|
+
def main(argv)
|
101
|
+
# Connect with AL
|
102
|
+
#
|
103
|
+
config = {
|
104
|
+
:host => LDAP_SERVER,
|
105
|
+
:port => LDAP_PORT,
|
106
|
+
:base => LDAP_BASE,
|
107
|
+
}
|
108
|
+
|
109
|
+
do_populate = LDAP_USER && LDAP_PASSWORD
|
110
|
+
|
111
|
+
if do_populate
|
112
|
+
config[:bind_format] = LDAP_USER
|
113
|
+
config[:password] = LDAP_PASSWORD
|
114
|
+
end
|
115
|
+
ActiveLdap::Base.establish_connection(config)
|
116
|
+
|
117
|
+
if do_populate
|
118
|
+
puts "populating..."
|
119
|
+
dumped_data = ActiveLdap::Base.dump(:scope => :sub)
|
120
|
+
ActiveLdap::Base.delete_all(nil, :scope => :sub)
|
121
|
+
populate
|
122
|
+
end
|
123
|
+
|
124
|
+
# Standard connection
|
125
|
+
#
|
126
|
+
conn = LDAP::Conn.new(LDAP_SERVER, LDAP_PORT)
|
127
|
+
al_count = 0
|
128
|
+
al_count_without_object_creation = 0
|
129
|
+
ldap_count = 0
|
130
|
+
Benchmark.bm(10) do |x|
|
131
|
+
x.report("AL") { al_count = search_al }
|
132
|
+
x.report("AL(No Obj)") do
|
133
|
+
al_count_without_object_creation = search_al_without_object_creation
|
134
|
+
end
|
135
|
+
x.report("LDAP") { ldap_count = search_ldap(conn) }
|
136
|
+
end
|
137
|
+
print "Entries processed by Ruby/ActiveLdap: #{al_count}\n"
|
138
|
+
print "Entries processed by Ruby/ActiveLdap (without object creation)" +
|
139
|
+
": #{al_count_without_object_creation}\n"
|
140
|
+
print "Entries processed by Ruby/LDAP: #{ldap_count}\n"
|
141
|
+
|
142
|
+
0
|
143
|
+
ensure
|
144
|
+
if do_populate
|
145
|
+
ActiveLdap::Base.delete_all(nil, :scope => :sub)
|
146
|
+
ActiveLdap::Base.load(dumped_data)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
if $0 == __FILE__ then
|
151
|
+
exit(main(ARGV) || 1)
|
152
|
+
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
|
-
# = Ruby/
|
2
|
+
# = Ruby/ActiveLdap
|
3
3
|
#
|
4
|
-
# "Ruby/
|
4
|
+
# "Ruby/ActiveLdap" Copyright (C) 2004,2005 Will Drewry mailto:will@alum.bu.edu
|
5
5
|
#
|
6
6
|
# == Introduction
|
7
7
|
#
|
8
|
-
# Ruby/
|
8
|
+
# Ruby/ActiveLdap is a novel way of interacting with LDAP. Most interaction with
|
9
9
|
# LDAP is done using clunky LDIFs, web interfaces, or with painful APIs that
|
10
|
-
# required a thick reference manual nearby. Ruby/
|
11
|
-
# Inspired by ActiveRecord[http://activerecord.rubyonrails.org], Ruby/
|
10
|
+
# required a thick reference manual nearby. Ruby/ActiveLdap aims to fix that.
|
11
|
+
# Inspired by ActiveRecord[http://activerecord.rubyonrails.org], Ruby/ActiveLdap provides an
|
12
12
|
# object oriented interface to LDAP entries.
|
13
13
|
#
|
14
14
|
# The target audience is system administrators and LDAP users everywhere that
|
@@ -29,22 +29,22 @@
|
|
29
29
|
# * RFC1777[http://www.faqs.org/rfcs/rfc1777.html] - Lightweight Directory Access Protocol
|
30
30
|
# * OpenLDAP[http://www.openldap.org]
|
31
31
|
#
|
32
|
-
# === So why use Ruby/
|
32
|
+
# === So why use Ruby/ActiveLdap?
|
33
33
|
#
|
34
34
|
# Well if you like to fumble around in the dark, dank innards of LDAP, you can
|
35
35
|
# quit reading now. However, if you'd like a cleaner way to integrate LDAP in to
|
36
|
-
# your existing code, hopefully that's why you'll want to use Ruby/
|
36
|
+
# your existing code, hopefully that's why you'll want to use Ruby/ActiveLdap.
|
37
37
|
#
|
38
38
|
# Using LDAP directly (even with the excellent Ruby/LDAP), leaves you bound to
|
39
39
|
# the world of the predefined LDAP API. While this API is important for many
|
40
40
|
# reasons, having to extract code out of LDAP search blocks and create huge
|
41
41
|
# arrays of LDAP.mod entries make code harder to read, less intuitive, and just
|
42
|
-
# less fun to write. Hopefully, Ruby/
|
42
|
+
# less fun to write. Hopefully, Ruby/ActiveLdap will remedy all of these
|
43
43
|
# problems!
|
44
44
|
#
|
45
45
|
# == Getting Started
|
46
46
|
#
|
47
|
-
# Ruby/
|
47
|
+
# Ruby/ActiveLdap does have some overhead when you get started. You must not
|
48
48
|
# only install the package and all of it's requirements, but you must also make
|
49
49
|
# customizations that will let it work in your environment.
|
50
50
|
#
|
@@ -63,12 +63,12 @@
|
|
63
63
|
# Assuming all the requirements are installed, you can install by grabbing the latest tgz file from
|
64
64
|
# the download site[http://projects.dataspill.org/libraries/ruby/activeldap/download.html].
|
65
65
|
#
|
66
|
-
# The following steps will get the Ruby/
|
66
|
+
# The following steps will get the Ruby/ActiveLdap installed in no time!
|
67
67
|
#
|
68
68
|
# $ tar -xzvf ruby-activeldap-current.tgz
|
69
69
|
# $ cd ruby-activeldap-VERSION
|
70
70
|
#
|
71
|
-
# Edit lib/
|
71
|
+
# Edit lib/active_ldap/configuration.rb replacing values to match what will work
|
72
72
|
# with your LDAP servers. Please note that those variables are required, but can
|
73
73
|
# be overridden in any program as detailed later in this document. Also make
|
74
74
|
# sure that "ROOT" stays all upcase.
|
@@ -82,7 +82,7 @@
|
|
82
82
|
# Now as a quick test, you can run:
|
83
83
|
#
|
84
84
|
# $ irb
|
85
|
-
# irb> require '
|
85
|
+
# irb> require 'active_ldap'
|
86
86
|
# => true
|
87
87
|
# irb> exit
|
88
88
|
#
|
@@ -93,7 +93,7 @@
|
|
93
93
|
#
|
94
94
|
# === Customizations
|
95
95
|
#
|
96
|
-
# Now that Ruby/
|
96
|
+
# Now that Ruby/ActiveLdap is installed and working, we still have a few more
|
97
97
|
# steps to make it useful for programming.
|
98
98
|
#
|
99
99
|
# Let's say that you are writing a Ruby program for managing user and group
|
@@ -119,46 +119,46 @@
|
|
119
119
|
#
|
120
120
|
# == Usage
|
121
121
|
#
|
122
|
-
# This section covers using Ruby/
|
122
|
+
# This section covers using Ruby/ActiveLdap from writing extension classes to
|
123
123
|
# writing applications that use them.
|
124
124
|
#
|
125
125
|
# Just to give a taste of what's to come, here is a quick example using irb:
|
126
126
|
#
|
127
|
-
# irb> require '
|
127
|
+
# irb> require 'active_ldap'
|
128
128
|
#
|
129
129
|
# Here's an extension class that maps to the LDAP Group objects:
|
130
130
|
#
|
131
|
-
# irb> class Group <
|
131
|
+
# irb> class Group < ActiveLdap::Base
|
132
132
|
# irb* ldap_mapping
|
133
133
|
# irb* end
|
134
134
|
#
|
135
135
|
# Here is the Group class in use:
|
136
136
|
#
|
137
|
-
# irb> all_groups = Group.
|
137
|
+
# irb> all_groups = Group.find(:all, '*').collect {|group| group.cn}
|
138
138
|
# => ["root", "daemon", "bin", "sys", "adm", "tty", ..., "develop"]
|
139
139
|
#
|
140
|
-
# irb> group = Group.
|
140
|
+
# irb> group = Group.find("develop")
|
141
141
|
# => #<Group:0x..........>
|
142
142
|
#
|
143
|
-
# irb> group.members
|
143
|
+
# irb> group.members.collect {|member| member.uid}
|
144
144
|
# => ["drewry"]
|
145
145
|
#
|
146
146
|
# irb> group.cn
|
147
147
|
# => "develop"
|
148
148
|
#
|
149
|
-
# irb> group.
|
149
|
+
# irb> group.gid_number
|
150
150
|
# => "1003"
|
151
151
|
#
|
152
152
|
# That's it! No let's get back in to it.
|
153
153
|
#
|
154
154
|
# === Extension Classes
|
155
155
|
#
|
156
|
-
# Extension classes are classes that are subclassed from
|
156
|
+
# Extension classes are classes that are subclassed from ActiveLdap::Base. They
|
157
157
|
# are used to represent objects in your LDAP server abstractly.
|
158
158
|
#
|
159
159
|
# ==== Why do I need them?
|
160
160
|
#
|
161
|
-
# Extension classes are what make Ruby/
|
161
|
+
# Extension classes are what make Ruby/ActiveLdap "active"! They do all the
|
162
162
|
# background work to make easy-to-use objects by mapping the LDAP object's
|
163
163
|
# attributes on to a Ruby class.
|
164
164
|
#
|
@@ -172,12 +172,13 @@
|
|
172
172
|
# ===== ldap_mapping
|
173
173
|
#
|
174
174
|
# ldap_mapping is the only required method to setup an extension class for use
|
175
|
-
# with Ruby/
|
175
|
+
# with Ruby/ActiveLdap. It must be called inside of a subclass as shown above.
|
176
176
|
#
|
177
177
|
# Below is a much more realistic Group class:
|
178
178
|
#
|
179
|
-
# class Group <
|
180
|
-
# ldap_mapping :
|
179
|
+
# class Group < ActiveLdap::Base
|
180
|
+
# ldap_mapping :dn_attribute => 'cn',
|
181
|
+
# :prefix => 'ou=Groups', :classes => ['top', 'posixGroup']
|
181
182
|
# :scope => LDAP::LDAP_SCOPE_ONELEVEL
|
182
183
|
# end
|
183
184
|
#
|
@@ -202,35 +203,33 @@
|
|
202
203
|
#
|
203
204
|
# cn=develop,ou=Groups,dc=dataspill,dc=org
|
204
205
|
# ^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
|
205
|
-
# :
|
206
|
+
# :dn_attribute | |
|
206
207
|
# :prefix |
|
207
208
|
# :base from configuration.rb
|
208
209
|
#
|
209
|
-
# :scope tells
|
210
|
+
# :scope tells ActiveLdap to only search under ou=Groups, and not to look deeper
|
210
211
|
# for dnattr matches. (e.g. cn=develop,ou=DevGroups,ou=Groups,dc=dataspill,dc=org)
|
211
212
|
#
|
212
|
-
# Something's missing: :classes. :classes is used to tell Ruby/
|
213
|
+
# Something's missing: :classes. :classes is used to tell Ruby/ActiveLdap what
|
213
214
|
# the minimum requirement is when creating a new object. LDAP uses objectClasses
|
214
|
-
# to define what attributes a LDAP object may have. Ruby/
|
215
|
+
# to define what attributes a LDAP object may have. Ruby/ActiveLdap needs to know
|
215
216
|
# what classes are required when creating a new object. Of course, you can leave
|
216
217
|
# that field out to default to ['top'] only. Then you can let each application
|
217
218
|
# choose what objectClasses their objects should have by calling the method e.g.
|
218
|
-
# Group#
|
219
|
-
# e.g. Group#objectClass.
|
219
|
+
# Group#add_class(*values).
|
220
220
|
#
|
221
221
|
# Note that is can be very important to define the default :classes value. Due to
|
222
222
|
# implementation choices with most LDAP servers, once an object is created, its
|
223
223
|
# structural objectclasses may not be removed (or replaced). Setting a sane default
|
224
224
|
# may help avoid programmer error later.
|
225
225
|
#
|
226
|
-
# :classes isn't the only optional argument. If :
|
227
|
-
# to 'cn'. If :prefix is left off,
|
228
|
-
#
|
229
|
-
#
|
230
|
-
# parent_class.new(parent_dn). The parent_dn is the objects dn without the dnattr
|
231
|
-
# pair.
|
226
|
+
# :classes isn't the only optional argument. If :dn_attribute is left off,
|
227
|
+
# it defaults to underscored class name or 'cn'. If :prefix is left off,
|
228
|
+
# it will default to 'ou=PLURALIZED_CLASSNAME'. In this
|
229
|
+
# case, it would be 'ou=Groups'.
|
232
230
|
#
|
233
|
-
# :classes should be an Array. :
|
231
|
+
# :classes should be an Array. :dn_attribute should be a String and so should
|
232
|
+
# :prefix.
|
234
233
|
#
|
235
234
|
#
|
236
235
|
# ===== belongs_to
|
@@ -249,7 +248,7 @@
|
|
249
248
|
# In the above tree, one such example would be user 'drewry' who is a part of the
|
250
249
|
# group 'develop'. You can see this by looking at the 'memberUid' field of 'develop'.
|
251
250
|
#
|
252
|
-
# irb> develop = Group.
|
251
|
+
# irb> develop = Group.find('develop')
|
253
252
|
# => ...
|
254
253
|
# irb> develop.memberUid
|
255
254
|
# => ['drewry', 'builder']
|
@@ -257,35 +256,28 @@
|
|
257
256
|
# If we look at the LDAP entry for 'drewry', we do not see any references to
|
258
257
|
# group 'develop'. In order to remedy that, we can use belongs_to
|
259
258
|
#
|
260
|
-
# irb> class User <
|
261
|
-
# irb* ldap_mapping :
|
262
|
-
# irb* belongs_to :groups, :
|
259
|
+
# irb> class User < ActiveLdap::Base
|
260
|
+
# irb* ldap_mapping :dn_attribute => 'uid', :prefix => 'People', :classes => ['top','account']
|
261
|
+
# irb* belongs_to :groups, :class => 'Group', :many => 'memberUid', :foreign_key => 'uid'
|
263
262
|
# irb* end
|
264
263
|
#
|
265
264
|
# Now, class User will have a method called 'groups' which will retrieve all
|
266
265
|
# Group objects that a user is in.
|
267
266
|
#
|
268
|
-
# irb> me = User.
|
267
|
+
# irb> me = User.find('drewry')
|
269
268
|
# irb> me.groups
|
270
269
|
# => [#<Group:0x000001 ...>, #<Group:0x000002 ...>, ...]
|
271
|
-
# irb> me.groups
|
270
|
+
# irb> me.groups.each { |group| p group.cn };nil
|
272
271
|
# "cdrom"
|
273
272
|
# "audio"
|
274
273
|
# "develop"
|
275
274
|
# => nil
|
276
275
|
# (Note: nil is just there to make the output cleaner...)
|
277
276
|
#
|
278
|
-
# Methods created with belongs_to also take an optional argument: objects. This
|
279
|
-
# argument specifies whether it will return the value of the 'dnattr' of the
|
280
|
-
# objects, or whether it will return Group objects.
|
281
|
-
#
|
282
|
-
# irb> me.groups(false)
|
283
|
-
# => ["cdrom", "audio", "develop"]
|
284
|
-
#
|
285
277
|
# TIP: If you weren't sure what the distinguished name attribute was for Group,
|
286
278
|
# you could also do the following:
|
287
279
|
#
|
288
|
-
# irb> me.groups.each { |group| p group.
|
280
|
+
# irb> me.groups.each { |group| p group.id };nil
|
289
281
|
# "cdrom"
|
290
282
|
# "audio"
|
291
283
|
# "develop"
|
@@ -294,18 +286,18 @@
|
|
294
286
|
# Now let's talk about the arguments. The first argument is the name of the
|
295
287
|
# method you wish to create. In this case, we created a method called groups
|
296
288
|
# using the symbol :groups. The next collection of arguments are actually a Hash
|
297
|
-
# (as with ldap_mapping). :
|
289
|
+
# (as with ldap_mapping). :class should be a string that has the name of a
|
298
290
|
# class you've already included. If you class is inside of a module, be sure to
|
299
|
-
# put the whole name, e.g. :
|
300
|
-
# tells belongs_to what attribute Group objects have that match the
|
301
|
-
# :
|
302
|
-
# in Group under the
|
303
|
-
# it is assumed to be the
|
304
|
-
# become:
|
305
|
-
#
|
306
|
-
# irb> class User <
|
291
|
+
# put the whole name, e.g. :class => "MyLdapModule::Group". :primary_key
|
292
|
+
# tells belongs_to what attribute Group objects have that match the
|
293
|
+
# :many. :many is the name of the local attribute whose value
|
294
|
+
# should be looked up in Group under the primary key. If :foreign_key is left
|
295
|
+
# off of the argument list, it is assumed to be the dn_attribute. With this in
|
296
|
+
# mind, the above definition could become:
|
297
|
+
#
|
298
|
+
# irb> class User < ActiveLdap::Base
|
307
299
|
# irb* ldap_mapping :dnattr => 'uid', :prefix => 'People', :classes => ['top','account']
|
308
|
-
# irb* belongs_to :groups, :
|
300
|
+
# irb* belongs_to :groups, :class => 'Group', :many => 'memberUid'
|
309
301
|
# irb* end
|
310
302
|
#
|
311
303
|
# In addition, you can do simple membership tests by doing the following:
|
@@ -322,26 +314,24 @@
|
|
322
314
|
# objects from other trees listed in your object. To show this, we can just
|
323
315
|
# invert the example from above:
|
324
316
|
#
|
325
|
-
# class Group <
|
326
|
-
# ldap_mapping :
|
327
|
-
# has_many :members, :
|
317
|
+
# class Group < ActiveLdap::Base
|
318
|
+
# ldap_mapping :dn_attribute => 'cn', :prefix => 'ou=Groups', :classes => ['top', 'posixGroup']
|
319
|
+
# has_many :members, :class => "User", :wrap => "memberUid", :primary_key => 'uid'
|
328
320
|
# end
|
329
321
|
#
|
330
322
|
# Now we can see that group develop has user 'drewry' as a member, and it can
|
331
323
|
# even return all responses in object form just like belongs_to methods.
|
332
324
|
#
|
333
|
-
# irb> develop = Group.
|
325
|
+
# irb> develop = Group.find('develop')
|
334
326
|
# => ...
|
335
327
|
# irb> develop.members
|
336
328
|
# => [#<User:0x000001 ...>, #<User:...>]
|
337
|
-
# irb> develop.members(false)
|
338
|
-
# => ["drewry", "builder"]
|
339
329
|
#
|
340
330
|
#
|
341
331
|
# The arguments for has_many follow the exact same idea that belongs_to's
|
342
|
-
# arguments followed. :
|
343
|
-
# :
|
344
|
-
#
|
332
|
+
# arguments followed. :wrap's contents are used to search for matching
|
333
|
+
# :primary_key content. If :primary_key is not specified, it defaults to the
|
334
|
+
# dn_attribute of the specified :class.
|
345
335
|
#
|
346
336
|
# === Using these new classes
|
347
337
|
#
|
@@ -351,37 +341,35 @@
|
|
351
341
|
# methods that do not fall in to these categories.
|
352
342
|
#
|
353
343
|
#
|
354
|
-
# ==== .find
|
344
|
+
# ==== .find
|
355
345
|
#
|
356
346
|
# .find is a class method that is accessible from any subclass of Base that has
|
357
347
|
# 'ldap_mapping' called. When called it returns the first match of the given
|
358
348
|
# class.
|
359
349
|
#
|
360
|
-
# irb> Group.find('*')
|
350
|
+
# irb> Group.find('*').cn
|
361
351
|
# => "root"
|
362
352
|
#
|
363
353
|
# In this simple example, Group.find took the search string of 'deve*' and
|
364
354
|
# searched for the first match in Group where the dnattr matched the query. This
|
365
355
|
# is the simplest example of .find.
|
366
356
|
#
|
367
|
-
# irb> Group.
|
357
|
+
# irb> Group.find(:all, '*').collect {|group| group.cn}
|
368
358
|
# => ["root", "daemon", "bin", "sys", "adm", "tty", ..., "develop"]
|
369
359
|
#
|
370
|
-
# Here .
|
371
|
-
# also can take more expressive arguments:
|
360
|
+
# Here .find(:all) returns all matches to the same query. Both .find and
|
361
|
+
# .find(:all) also can take more expressive arguments:
|
372
362
|
#
|
373
|
-
# irb> Group.
|
363
|
+
# irb> Group.find(:all, :attribute => 'gidNumber', :value => '1003').collect {|group| group.cn}
|
374
364
|
# => ["develop"]
|
375
365
|
#
|
376
366
|
# So it is pretty clear what :attribute and :value do - they are used to query as
|
377
|
-
# :attribute=:value.
|
378
|
-
# Class when it is set to true.
|
379
|
-
#
|
380
|
-
# irb> Group.find_all(:attribute => 'gidNumber', :value => '1003', :objects => false)
|
381
|
-
# => [#<Group:0x40674a70 ..>]
|
367
|
+
# :attribute=:value.
|
382
368
|
#
|
383
|
-
# If :
|
384
|
-
#
|
369
|
+
# If :attribute is unspecified, it defaults to the dn_attribute.
|
370
|
+
#
|
371
|
+
# It is also possible to override :attribute and :value by specifying :filter. This
|
372
|
+
# argument allows the direct specification of a LDAP filter to retrieve objects by.
|
385
373
|
#
|
386
374
|
# ==== .search
|
387
375
|
# .search is a class method that is accessible from any subclass of Base, and Base.
|
@@ -390,63 +378,59 @@
|
|
390
378
|
# to cover 80% of the cases where a user would want to use Base.connection directly.
|
391
379
|
#
|
392
380
|
# irb> Base.search(:base => 'dc=example,dc=com', :filter => '(uid=roo*)',
|
393
|
-
# :scope =>
|
394
|
-
# => [
|
395
|
-
# You can specify the :filter, :base, :scope, and :
|
381
|
+
# :scope => :sub, :attributes => ['uid', 'cn'])
|
382
|
+
# => [["uid=root,ou=People,dc=dataspill,dc=org",{"cn"=>["root"], "uidNumber"=>["0"]}]
|
383
|
+
# You can specify the :filter, :base, :scope, and :attributes, but they all have defaults --
|
396
384
|
# * :filter defaults to objectClass=* - usually this isn't what you want
|
397
385
|
# * :base defaults to the base of the class this is executed from (as set in ldap_mapping)
|
398
|
-
# * :scope defaults to
|
399
|
-
# * :
|
386
|
+
# * :scope defaults to :sub. Usually you won't need to change it
|
387
|
+
# * :attributes defaults to [] and is the list of attributes you want back. Empty means all of them.
|
400
388
|
#
|
401
|
-
# ==== #
|
389
|
+
# ==== #valid?
|
402
390
|
#
|
403
|
-
#
|
404
|
-
# objects current objectClasses are populated.
|
405
|
-
# private "#enforce_types" method. This will make sure that all values defined are
|
406
|
-
# valid to be written to LDAP. #validate is called by #write prior to
|
407
|
-
# performing any action. Its explicit use in an application is unnecessary, and
|
408
|
-
# it may become a private method in the future.
|
391
|
+
# valid? is a method that verifies that all attributes that are required by the
|
392
|
+
# objects current objectClasses are populated.
|
409
393
|
#
|
410
|
-
# ==== #
|
394
|
+
# ==== #save
|
411
395
|
#
|
412
|
-
#
|
396
|
+
# save is a method that writes any changes to an object back to the LDAP server.
|
413
397
|
# It automatically handles the addition of new objects, and the modification of
|
414
398
|
# existing ones.
|
415
399
|
#
|
416
|
-
# ====
|
400
|
+
# ==== .exists?
|
417
401
|
#
|
418
402
|
# exists? is a simple method which returns true is the current object exists in
|
419
403
|
# LDAP, or false if it does not.
|
420
404
|
#
|
421
|
-
# irb>
|
422
|
-
# => ...
|
423
|
-
# irb> newuser.exists?
|
405
|
+
# irb> User.exists?("dshadsadsa")
|
424
406
|
# => false
|
425
407
|
#
|
426
408
|
#
|
427
|
-
# ===
|
409
|
+
# === ActiveLdap::Base
|
428
410
|
#
|
429
|
-
#
|
411
|
+
# ActiveLdap::Base has come up a number of times in the examples above. Every
|
430
412
|
# time, it was being used as the super class for the wrapper objects. While this
|
431
413
|
# is it's main purpose, it also handles quite a bit more in the background.
|
432
414
|
#
|
433
415
|
# ==== What is it?
|
434
416
|
#
|
435
|
-
#
|
417
|
+
# ActiveLdap::Base is the heart of Ruby/ActiveLdap. It does all the schema
|
436
418
|
# parsing for validation and attribute-to-method mangling as well as manage the
|
437
419
|
# connection to LDAP.
|
438
420
|
#
|
439
|
-
# =====
|
421
|
+
# ===== establish_connection
|
440
422
|
#
|
441
|
-
# Base.
|
442
|
-
# server. Sometimes you will want to connect anonymously
|
443
|
-
# with user credentials. Base.connect is here to do
|
423
|
+
# Base.establish_connection takes many (optional) arguments and is used to
|
424
|
+
# connect to the LDAP server. Sometimes you will want to connect anonymously
|
425
|
+
# and other times over TLS with user credentials. Base.connect is here to do
|
426
|
+
# all of that for you.
|
444
427
|
#
|
445
428
|
#
|
446
|
-
# By default, if you call any subclass of Base, such as Group, it will call
|
447
|
-
# Base.
|
448
|
-
# anonymous binding, and you only want to access data in a
|
449
|
-
# won't need to call Base.
|
429
|
+
# By default, if you call any subclass of Base, such as Group, it will call
|
430
|
+
# Base.establish_connection() if these is no active LDAP connection. If your
|
431
|
+
# server allows anonymous binding, and you only want to access data in a
|
432
|
+
# read-only fashion, you won't need to call Base.establish_connection. Here
|
433
|
+
# is a fully parameterized call:
|
450
434
|
#
|
451
435
|
# Base.connect(
|
452
436
|
# :host => 'ldap.dataspill.org',
|
@@ -490,18 +474,18 @@
|
|
490
474
|
# whether the :password_block should be called on each reconnect.
|
491
475
|
# * :allow_anonymous determines whether anonymous binding is allowed if other
|
492
476
|
# bind methods fail
|
493
|
-
# * :try_sasl, when true, tells
|
477
|
+
# * :try_sasl, when true, tells ActiveLdap to attempt a SASL-GSSAPI bind
|
494
478
|
# * :sasl_quiet, when true, tells the SASL libraries to not spew messages to STDOUT
|
495
479
|
# * :method indicates whether to use :ssl, :tls, or :plain
|
496
480
|
# * :retries - indicates the number of attempts to reconnect that will be undertaken when a stale connection occurs. -1 means infinite.
|
497
481
|
# * :retry_wait - seconds to wait before retrying a connection
|
498
482
|
# * :ldap_scope - dictates how to find objects. (Default: ONELEVEL)
|
499
|
-
# * :return_objects - indicates whether find/find_all will return objects or just the distinguished name attribute value of the matches. Rails users will find this useful.
|
500
483
|
# * :timeout - time in seconds - defaults to disabled. This CAN interrupt search() requests. Be warned.
|
501
484
|
# * :retry_on_timeout - whether to reconnect when timeouts occur. Defaults to true
|
502
485
|
# See lib/configuration.rb for defaults for each option
|
503
486
|
#
|
504
|
-
# Base.
|
487
|
+
# Base.establish_connection both connects and binds in one step. It follows
|
488
|
+
# roughly the following approach:
|
505
489
|
#
|
506
490
|
# * Connect to host:port using :method
|
507
491
|
#
|
@@ -511,41 +495,32 @@
|
|
511
495
|
# * If that fails, error out.
|
512
496
|
#
|
513
497
|
# On connect, the configuration options passed in are stored in an internal class variable
|
514
|
-
#
|
498
|
+
# @configuration which is used to cache the information without ditching the defaults passed in
|
515
499
|
# from configuration.rb
|
516
500
|
#
|
517
|
-
# ===== close
|
518
|
-
#
|
519
|
-
# Base.close discards the current LDAP connection.
|
520
|
-
#
|
521
501
|
# ===== connection
|
522
502
|
#
|
523
|
-
# Base.connection returns the
|
503
|
+
# Base.connection returns the ActiveLdap::Connection object.
|
524
504
|
#
|
525
505
|
# === Exceptions
|
526
506
|
#
|
527
|
-
# There are a few custom exceptions used in Ruby/
|
528
|
-
#
|
529
|
-
# ==== AttributeEmpty
|
530
|
-
#
|
531
|
-
# This exception is raised when a required attribute is empty. It is only raised
|
532
|
-
# by #validate, and by proxy, #write.
|
507
|
+
# There are a few custom exceptions used in Ruby/ActiveLdap. They are detailed below.
|
533
508
|
#
|
534
509
|
# ==== DeleteError
|
535
510
|
#
|
536
511
|
# This exception is raised when #delete fails. It will include LDAP error
|
537
512
|
# information that was passed up during the error.
|
538
513
|
#
|
539
|
-
# ====
|
514
|
+
# ==== SaveError
|
540
515
|
#
|
541
|
-
# This exception is raised when there is a problem in #
|
516
|
+
# This exception is raised when there is a problem in #save updating or creating
|
542
517
|
# an LDAP entry. Often the error messages are cryptic. Looking at the server
|
543
518
|
# logs or doing an Ethereal[http://www.ethereal.com] dump of the connection will
|
544
519
|
# often provide better insight.
|
545
520
|
#
|
546
521
|
# ==== AuthenticationError
|
547
522
|
#
|
548
|
-
# This exception is raised during Base.
|
523
|
+
# This exception is raised during Base.establish_connection if no valid authentication methods
|
549
524
|
# succeeded.
|
550
525
|
#
|
551
526
|
# ==== ConnectionError
|
@@ -569,7 +544,7 @@
|
|
569
544
|
#
|
570
545
|
# === Putting it all together
|
571
546
|
#
|
572
|
-
# Now that all of the components of Ruby/
|
547
|
+
# Now that all of the components of Ruby/ActiveLdap have been covered, it's time
|
573
548
|
# to put it all together! The rest of this section will show the steps to setup
|
574
549
|
# example user and group management scripts for use with the LDAP tree described
|
575
550
|
# above.
|
@@ -580,18 +555,18 @@
|
|
580
555
|
#
|
581
556
|
# In ldapadmin/lib/ create the file user.rb:
|
582
557
|
# cat <<EOF
|
583
|
-
# class User <
|
584
|
-
# ldap_mapping :
|
585
|
-
# belongs_to :groups, :
|
558
|
+
# class User < ActiveLdap::Base
|
559
|
+
# ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People', :classes => ['top', 'account', 'posixAccount']
|
560
|
+
# belongs_to :groups, :class => 'Group', :wrap => 'memberUid'
|
586
561
|
# end
|
587
562
|
# EOF
|
588
563
|
#
|
589
564
|
# In ldapadmin/lib/ create the file group.rb:
|
590
565
|
# cat <<EOF
|
591
|
-
# class Group <
|
566
|
+
# class Group < ActiveLdap::Base
|
592
567
|
# ldap_mapping :classes => ['top', 'posixGroup'], :prefix => 'ou=Group'
|
593
|
-
# has_many :members, :
|
594
|
-
#
|
568
|
+
# has_many :members, :class => "User", :many => "memberUid"
|
569
|
+
# has_many :primary_members, :class => 'User', :foreign_key => 'gidNumber', :primary_key => 'gidNumber'
|
595
570
|
# end # Group
|
596
571
|
# EOF
|
597
572
|
#
|
@@ -603,57 +578,90 @@
|
|
603
578
|
#
|
604
579
|
# #!/usr/bin/ruby -W0
|
605
580
|
#
|
606
|
-
# require '
|
581
|
+
# require 'active_ldap'
|
607
582
|
# require 'lib/user'
|
608
583
|
# require 'lib/group'
|
609
584
|
# require 'password'
|
610
585
|
#
|
611
|
-
#
|
612
|
-
#
|
613
|
-
#
|
614
|
-
#
|
615
|
-
#
|
616
|
-
#
|
617
|
-
#
|
618
|
-
#
|
619
|
-
#
|
620
|
-
#
|
621
|
-
#
|
622
|
-
#
|
623
|
-
#
|
624
|
-
#
|
625
|
-
#
|
626
|
-
#
|
627
|
-
#
|
628
|
-
#
|
586
|
+
# argv, opts, options = ActiveLdap::Command.parse_options do |opts, options|
|
587
|
+
# opts.banner += " USER_NAME CN UID"
|
588
|
+
# end
|
589
|
+
#
|
590
|
+
# if argv.size == 3
|
591
|
+
# name, cn, uid = argv
|
592
|
+
# else
|
593
|
+
# $stderr.puts opts
|
594
|
+
# exit 1
|
595
|
+
# end
|
596
|
+
#
|
597
|
+
# pwb = Proc.new do |user|
|
598
|
+
# ActiveLdap::Command.read_password("[#{user}] Password: ")
|
599
|
+
# end
|
600
|
+
#
|
601
|
+
# ActiveLdap::Base.establish_connection(:password_block => pwb,
|
602
|
+
# :allow_anonymous => false)
|
603
|
+
#
|
604
|
+
# if User.exists?(name)
|
605
|
+
# $stderr.puts("User #{name} already exists.")
|
606
|
+
# exit 1
|
607
|
+
# end
|
608
|
+
#
|
609
|
+
# user = User.new(name)
|
610
|
+
# user.add_class('shadowAccount')
|
611
|
+
# user.cn = cn
|
612
|
+
# user.uid_number = uid
|
613
|
+
# user.gid_number = uid
|
614
|
+
# user.home_directory = "/home/#{name}"
|
615
|
+
# user.sn = "somesn"
|
616
|
+
# unless user.save
|
617
|
+
# puts "failed"
|
618
|
+
# puts user.errors.full_messages
|
619
|
+
# exit 1
|
620
|
+
# end
|
621
|
+
#
|
629
622
|
# ==== Managing LDAP entries
|
630
623
|
#
|
631
624
|
# Now let's create another dumb script for modifying users - ldapadmin/usermod:
|
632
625
|
#
|
633
626
|
# #!/usr/bin/ruby -W0
|
634
627
|
#
|
635
|
-
# require '
|
628
|
+
# require 'active_ldap'
|
636
629
|
# require 'lib/user'
|
637
630
|
# require 'lib/group'
|
638
|
-
# require 'password'
|
639
|
-
#
|
640
|
-
# (printf($stderr, "Usage:\n%s name cn uid\n", $0); exit 1) if ARGV.size != 3
|
641
|
-
#
|
642
|
-
# puts "Changing user #{ARGV[0]}"
|
643
|
-
# pwb = Proc.new {
|
644
|
-
# Password.get('Password: ')
|
645
|
-
# }
|
646
|
-
# ActiveLDAP::Base.connect(:password_block => pwb, :allow_anonymous => false)
|
647
|
-
# user = User.new(ARGV[0])
|
648
|
-
# user.cn = ARGV[1]
|
649
|
-
# user.uidNumber = ARGV[2]
|
650
|
-
# user.gidNumber = ARGV[2]
|
651
|
-
# user.write
|
652
|
-
# puts "Modification successful!"
|
653
|
-
# exit 0
|
654
|
-
#
|
655
|
-
#
|
656
631
|
#
|
632
|
+
# argv, opts, options = ActiveLdap::Command.parse_options do |opts, options|
|
633
|
+
# opts.banner += " USER_NAME CN UID"
|
634
|
+
# end
|
635
|
+
#
|
636
|
+
# if argv.size == 3
|
637
|
+
# name, cn, uid = argv
|
638
|
+
# else
|
639
|
+
# $stderr.puts opts
|
640
|
+
# exit 1
|
641
|
+
# end
|
642
|
+
#
|
643
|
+
# pwb = Proc.new do |user|
|
644
|
+
# ActiveLdap::Command.read_password("[#{user}] Password: ")
|
645
|
+
# end
|
646
|
+
#
|
647
|
+
# ActiveLdap::Base.establish_connection(:password_block => pwb,
|
648
|
+
# :allow_anonymous => false)
|
649
|
+
#
|
650
|
+
# unless User.exists?(name)
|
651
|
+
# $stderr.puts("User #{name} doesn't exist.")
|
652
|
+
# exit 1
|
653
|
+
# end
|
654
|
+
#
|
655
|
+
# user = User.find(name)
|
656
|
+
# user.cn = cn
|
657
|
+
# user.uid_number = uid
|
658
|
+
# user.gid_number = uid
|
659
|
+
# unless user.save
|
660
|
+
# puts "failed"
|
661
|
+
# puts user.errors.full_messages
|
662
|
+
# exit 1
|
663
|
+
# end
|
664
|
+
#
|
657
665
|
# ==== Removing LDAP entries
|
658
666
|
#
|
659
667
|
# And finally, a dumb script for removing user - ldapadmin/userdel:
|
@@ -661,26 +669,38 @@
|
|
661
669
|
#
|
662
670
|
# #!/usr/bin/ruby -W0
|
663
671
|
#
|
664
|
-
# require '
|
672
|
+
# require 'active_ldap'
|
665
673
|
# require 'lib/user'
|
666
674
|
# require 'lib/group'
|
667
|
-
# require 'password'
|
668
|
-
#
|
669
|
-
# (printf($stderr, "Usage:\n%s name\n", $0); exit 1) if ARGV.size != 1
|
670
|
-
#
|
671
|
-
# puts "Changing user #{ARGV[0]}"
|
672
|
-
# pwb = Proc.new {
|
673
|
-
# Password.get('Password: ')
|
674
|
-
# }
|
675
|
-
# ActiveLDAP::Base.connect(:password_block => pwb, :allow_anonymous => false)
|
676
|
-
# user = User.new(ARGV[0])
|
677
|
-
# user.delete
|
678
|
-
# puts "User has been delete"
|
679
|
-
# exit 0
|
680
675
|
#
|
676
|
+
# argv, opts, options = ActiveLdap::Command.parse_options do |opts, options|
|
677
|
+
# opts.banner += " USER_NAME"
|
678
|
+
# end
|
679
|
+
#
|
680
|
+
# if argv.size == 1
|
681
|
+
# name = argv.shift
|
682
|
+
# else
|
683
|
+
# $stderr.puts opts
|
684
|
+
# exit 1
|
685
|
+
# end
|
686
|
+
#
|
687
|
+
# pwb = Proc.new do |user|
|
688
|
+
# ActiveLdap::Command.read_password("[#{user}] Password: ")
|
689
|
+
# end
|
690
|
+
#
|
691
|
+
# ActiveLdap::Base.establish_connection(:password_block => pwb,
|
692
|
+
# :allow_anonymous => false)
|
693
|
+
#
|
694
|
+
# unless User.exists?(name)
|
695
|
+
# $stderr.puts("User #{name} doesn't exist.")
|
696
|
+
# exit 1
|
697
|
+
# end
|
698
|
+
#
|
699
|
+
# User.destroy(name)
|
700
|
+
#
|
681
701
|
# === Advanced Topics
|
682
702
|
#
|
683
|
-
# Below are some situation tips and tricks to get the most out of Ruby/
|
703
|
+
# Below are some situation tips and tricks to get the most out of Ruby/ActiveLdap.
|
684
704
|
#
|
685
705
|
#
|
686
706
|
# ==== Binary data and other subtypes
|
@@ -697,9 +717,9 @@
|
|
697
717
|
# irb> user.cn
|
698
718
|
# => ["wad", {"lang-en-us" => ["wad", "Will Drewry"]}]
|
699
719
|
# # Now let's add a binary X.509 certificate (assume objectClass is correct)
|
700
|
-
# irb> user.
|
720
|
+
# irb> user.user_certificate = File.read('example.der')
|
701
721
|
# => ...
|
702
|
-
# irb> user.
|
722
|
+
# irb> user.save
|
703
723
|
#
|
704
724
|
# So that's a lot to take in. Here's what is going on. I just set the LDAP
|
705
725
|
# object's cn to "wad" and cn:lang-en-us to ["wad", "Will Drewry"].
|
@@ -711,11 +731,11 @@
|
|
711
731
|
# from breaking, and my code from crying. For correctness, I could have easily
|
712
732
|
# done the following:
|
713
733
|
#
|
714
|
-
# irb> user.
|
734
|
+
# irb> user.user_certificate = {'binary' => File.read('example.der')}
|
715
735
|
#
|
716
736
|
# You should note that some binary data does not use the binary subtype all the time.
|
717
737
|
# One example is jpegPhoto. You can use it as jpegPhoto;binary or just as jpegPhoto.
|
718
|
-
# Since the schema dictates that it is a binary value, Ruby/
|
738
|
+
# Since the schema dictates that it is a binary value, Ruby/ActiveLdap will write
|
719
739
|
# it as binary, but the subtype will not be automatically appended as above. The
|
720
740
|
# use of the subtype on attributes like jpegPhoto is ultimately decided by the
|
721
741
|
# LDAP site policy and not by any programmatic means.
|
@@ -742,7 +762,7 @@
|
|
742
762
|
# Example:
|
743
763
|
#
|
744
764
|
# ./myldap.rb:
|
745
|
-
# require '
|
765
|
+
# require 'active_ldap'
|
746
766
|
# require 'myldap/user'
|
747
767
|
# require 'myldap/group'
|
748
768
|
# module MyLDAP
|
@@ -750,18 +770,18 @@
|
|
750
770
|
#
|
751
771
|
# ./myldap/user.rb:
|
752
772
|
# module MyLDAP
|
753
|
-
# class User <
|
754
|
-
# ldap_mapping :
|
755
|
-
# belongs_to :groups, :
|
773
|
+
# class User < ActiveLdap::Base
|
774
|
+
# ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People', :classes => ['top', 'account', 'posixAccount']
|
775
|
+
# belongs_to :groups, :class => 'MyLDAP::Group', :many => 'memberUid'
|
756
776
|
# end
|
757
777
|
# end
|
758
778
|
#
|
759
779
|
# ./myldap/group.rb:
|
760
780
|
# module MyLDAP
|
761
|
-
# class Group <
|
781
|
+
# class Group < ActiveLdap::Base
|
762
782
|
# ldap_mapping :classes => ['top', 'posixGroup'], :prefix => 'ou=Group'
|
763
|
-
# has_many :members, :
|
764
|
-
#
|
783
|
+
# has_many :members, :class => 'MyLDAP::User', :wrap => 'memberUid'
|
784
|
+
# has_many :primary_members, :class => 'MyLDAP::User', :foreign_key => 'gidNumber', :primary_key => 'gidNumber'
|
765
785
|
# end
|
766
786
|
# end
|
767
787
|
#
|
@@ -775,68 +795,43 @@
|
|
775
795
|
# and everything should work well.
|
776
796
|
#
|
777
797
|
#
|
778
|
-
# ====
|
798
|
+
# ==== force array results for single values
|
779
799
|
#
|
780
|
-
# Even though Ruby/
|
781
|
-
# returning Array values only. By specifying '
|
782
|
-
# any attribute method you will get back a
|
783
|
-
# This is useful when you are just dumping values for human reading.
|
800
|
+
# Even though Ruby/ActiveLdap attempts to maintain programmatic ease by
|
801
|
+
# returning Array values only. By specifying 'true' as an argument to
|
802
|
+
# any attribute method you will get back a Array if it is single value.
|
784
803
|
# Here's an example:
|
785
804
|
#
|
786
805
|
# irb> user = User.new('drewry')
|
787
806
|
# => ...
|
788
|
-
# irb> user.cn(
|
789
|
-
# => "Will Drewry"
|
807
|
+
# irb> user.cn(true)
|
808
|
+
# => ["Will Drewry"]
|
790
809
|
#
|
791
|
-
# That's it. Now you can make human-readable output faster.
|
792
|
-
#
|
793
810
|
# ==== Dynamic attribute crawling
|
794
811
|
#
|
795
812
|
# If you use tab completion in irb, you'll notice that you /can/ tab complete the dynamic
|
796
813
|
# attribute methods. You can still see which methods are for attributes using
|
797
|
-
# Base#
|
814
|
+
# Base#attribute_names:
|
798
815
|
#
|
799
816
|
# irb> d = Group.new('develop')
|
800
817
|
# => ...
|
801
|
-
# irb> d.
|
818
|
+
# irb> d.attribute_names
|
802
819
|
# => ["gidNumber", "cn", "memberUid", "commonName", "description", "userPassword", "objectClass"]
|
803
820
|
#
|
804
821
|
#
|
805
|
-
# ==== Advanced LDAP queries
|
806
|
-
#
|
807
|
-
# With the addition of Base.search, you can do arbitrary queries against LDAP
|
808
|
-
# without needed to understand how to use Ruby/LDAP.
|
809
|
-
#
|
810
|
-
# If that's still not enough, you can access the
|
811
|
-
# Ruby/LDAP connection object using the class method Base.connection. You can
|
812
|
-
# do all of your LDAP specific calls here and then continue about your normal
|
813
|
-
# Ruby/ActiveLDAP business afterward.
|
814
|
-
#
|
815
|
-
#
|
816
|
-
# ==== Reusing LDAP::Entry objects without reusing the LDAP connection
|
817
|
-
#
|
818
|
-
# You can call Klass.new(entry) where Klass is some subclass of Base and
|
819
|
-
# enty is an LDAP::entry. This use of 'new' is is meant for use from
|
820
|
-
# within find_all and find, but you can also use it in tandem with advanced
|
821
|
-
# LDAP queries.
|
822
|
-
#
|
823
|
-
# See tests/benchmark for more insight.
|
824
|
-
#
|
825
822
|
# ==== Juggling multiple LDAP connections
|
826
823
|
#
|
827
824
|
# In the same vein as the last tip, you can use multiple LDAP connections by
|
828
|
-
#
|
825
|
+
# per class as follows:
|
829
826
|
#
|
830
|
-
# irb>
|
827
|
+
# irb> anon_class = Class.new(Base)
|
831
828
|
# => ...
|
832
|
-
# irb>
|
829
|
+
# irb> anon_class.establish_connection
|
833
830
|
# => ...
|
834
|
-
# irb>
|
831
|
+
# irb> auth_class = Class.new(Base)
|
835
832
|
# => ...
|
836
|
-
# irb>
|
833
|
+
# irb> auth_class.establish_connection(password_block => {'mypass'})
|
837
834
|
# => ...
|
838
|
-
# irb> Base.connection = anon_conn
|
839
|
-
# ...
|
840
835
|
#
|
841
836
|
# This can be useful for doing authentication tests and other such tricks.
|
842
837
|
#
|
@@ -855,7 +850,7 @@
|
|
855
850
|
# experiment. That's exactly how I ended up with this package. If you come up
|
856
851
|
# with something cool, please share it!
|
857
852
|
#
|
858
|
-
# The internal structure of
|
853
|
+
# The internal structure of ActiveLdap::Base, and thus all its subclasses, is
|
859
854
|
# still in flux. I've tried to minimize the changes to the overall API, but
|
860
855
|
# the internals are still rough around the edges.
|
861
856
|
#
|
@@ -865,37 +860,37 @@
|
|
865
860
|
# from Base:
|
866
861
|
# * Base.base()
|
867
862
|
# * Base.required_classes()
|
868
|
-
# * Base.
|
863
|
+
# * Base.dn_attribute()
|
869
864
|
# You can access these from custom class methods by calling MyClass.base(),
|
870
865
|
# or whatever. There are predefined instance methods for getting to these
|
871
866
|
# from any new instance methods you define:
|
872
867
|
# * Base#base()
|
873
868
|
# * Base#required_classes()
|
874
|
-
# * Base#
|
869
|
+
# * Base#dn_attribute()
|
875
870
|
#
|
876
871
|
# ===== What else?
|
877
872
|
#
|
878
873
|
# Well if you want to use the LDAP connection for anything, I'd suggest still
|
879
874
|
# calling Base.connection to get it. There really aren't many other internals
|
880
|
-
# that need to be worried about. You could
|
881
|
-
#
|
875
|
+
# that need to be worried about. You could get the LDAP schema with
|
876
|
+
# Base.schema.
|
882
877
|
#
|
883
878
|
# The only other useful tricks are dereferencing and accessing the stored
|
884
879
|
# data. Since LDAP attributes can have multiple names, e.g. cn or commonName,
|
885
880
|
# any methods you write might need to figure it out. I'd suggest just
|
886
|
-
# calling self
|
887
|
-
# you can call look up the stored name
|
888
|
-
# irb>
|
881
|
+
# calling self[attribname] to get the value, but if that's not good enough,
|
882
|
+
# you can call look up the stored name by #to_real_attribute_name as follows:
|
883
|
+
# irb> to_real_attribute_name('commonName')
|
889
884
|
# => 'cn'
|
890
885
|
#
|
891
886
|
# This tells you the name the attribute is stored in behind the scenes (@data).
|
892
|
-
# Again, self
|
887
|
+
# Again, self[attribname] should be enough for most extensions, but if not,
|
893
888
|
# it's probably safe to dabble here.
|
894
889
|
#
|
895
890
|
# Also, if you like to look up all aliases for an attribute, you can call the
|
896
891
|
# following:
|
897
892
|
#
|
898
|
-
# irb> attribute_aliases('cn')
|
893
|
+
# irb> schema.attribute_aliases('cn')
|
899
894
|
# => ['cn','commonName']
|
900
895
|
#
|
901
896
|
# This is discovered automagically from the LDAP server's schema.
|
@@ -904,7 +899,7 @@
|
|
904
899
|
#
|
905
900
|
# === Speed
|
906
901
|
#
|
907
|
-
# Currently, Ruby/
|
902
|
+
# Currently, Ruby/ActiveLdap could be faster. I have some recursive type
|
908
903
|
# checking going on which slows object creation down, and I'm sure there
|
909
904
|
# are many, many other places optimizations can be done. Feel free
|
910
905
|
# to send patches, or just hang in there until I can optimize away the
|
@@ -919,25 +914,47 @@
|
|
919
914
|
# Blanket warning hiding. Remove for debugging
|
920
915
|
$VERBOSE, verbose = false, $VERBOSE
|
921
916
|
|
922
|
-
require 'activeldap/ldap'
|
923
|
-
require 'activeldap/schema2'
|
924
917
|
if RUBY_PLATFORM.match('linux')
|
925
|
-
require '
|
918
|
+
require 'active_ldap/timeout'
|
926
919
|
else
|
927
|
-
require '
|
920
|
+
require 'active_ldap/timeout_stub'
|
921
|
+
end
|
922
|
+
|
923
|
+
require_gem_if_need = Proc.new do |library_name, gem_name|
|
924
|
+
begin
|
925
|
+
require library_name
|
926
|
+
rescue LoadError
|
927
|
+
require 'rubygems'
|
928
|
+
require_gem gem_name
|
929
|
+
require library_name
|
930
|
+
end
|
928
931
|
end
|
929
|
-
require 'activeldap/base'
|
930
|
-
require 'activeldap/associations'
|
931
|
-
require 'activeldap/configuration'
|
932
932
|
|
933
|
+
require_gem_if_need.call("active_support", "activesupport")
|
934
|
+
require 'active_ldap/base'
|
935
|
+
require 'active_ldap/associations'
|
936
|
+
require 'active_ldap/configuration'
|
937
|
+
require 'active_ldap/connection'
|
938
|
+
require 'active_ldap/attributes'
|
939
|
+
require 'active_ldap/object_class'
|
940
|
+
require 'active_ldap/adaptor/ldap'
|
941
|
+
|
942
|
+
require_gem_if_need.call("active_record/base", "activerecord")
|
943
|
+
require 'active_ldap/validations'
|
944
|
+
require 'active_ldap/callbacks'
|
933
945
|
|
934
|
-
module
|
935
|
-
VERSION = "0.
|
946
|
+
module ActiveLdap
|
947
|
+
VERSION = "0.8.0"
|
936
948
|
end
|
937
949
|
|
938
|
-
|
939
|
-
include
|
940
|
-
include
|
950
|
+
ActiveLdap::Base.class_eval do
|
951
|
+
include ActiveLdap::Configuration
|
952
|
+
include ActiveLdap::Connection
|
953
|
+
include ActiveLdap::Attributes
|
954
|
+
include ActiveLdap::ObjectClass
|
955
|
+
include ActiveLdap::Associations
|
956
|
+
include ActiveLdap::Validations
|
957
|
+
include ActiveLdap::Callbacks
|
941
958
|
end
|
942
959
|
|
943
960
|
$VERBOSE = verbose
|