ruby-activeldap 0.7.4 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|