ldaptic 0.2.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/LICENSE +20 -0
- data/README.rdoc +104 -0
- data/Rakefile +41 -0
- data/lib/ldaptic.rb +151 -0
- data/lib/ldaptic/active_model.rb +37 -0
- data/lib/ldaptic/adapters.rb +90 -0
- data/lib/ldaptic/adapters/abstract_adapter.rb +123 -0
- data/lib/ldaptic/adapters/active_directory_adapter.rb +78 -0
- data/lib/ldaptic/adapters/active_directory_ext.rb +12 -0
- data/lib/ldaptic/adapters/ldap_conn_adapter.rb +262 -0
- data/lib/ldaptic/adapters/net_ldap_adapter.rb +173 -0
- data/lib/ldaptic/adapters/net_ldap_ext.rb +24 -0
- data/lib/ldaptic/attribute_set.rb +283 -0
- data/lib/ldaptic/dn.rb +365 -0
- data/lib/ldaptic/entry.rb +646 -0
- data/lib/ldaptic/error_set.rb +34 -0
- data/lib/ldaptic/errors.rb +136 -0
- data/lib/ldaptic/escape.rb +110 -0
- data/lib/ldaptic/filter.rb +282 -0
- data/lib/ldaptic/methods.rb +387 -0
- data/lib/ldaptic/railtie.rb +9 -0
- data/lib/ldaptic/schema.rb +246 -0
- data/lib/ldaptic/syntaxes.rb +319 -0
- data/test/core.schema +582 -0
- data/test/ldaptic_active_model_test.rb +40 -0
- data/test/ldaptic_adapters_test.rb +35 -0
- data/test/ldaptic_attribute_set_test.rb +57 -0
- data/test/ldaptic_dn_test.rb +110 -0
- data/test/ldaptic_entry_test.rb +22 -0
- data/test/ldaptic_errors_test.rb +23 -0
- data/test/ldaptic_escape_test.rb +47 -0
- data/test/ldaptic_filter_test.rb +53 -0
- data/test/ldaptic_hierarchy_test.rb +90 -0
- data/test/ldaptic_schema_test.rb +44 -0
- data/test/ldaptic_syntaxes_test.rb +66 -0
- data/test/mock_adapter.rb +47 -0
- data/test/rbslapd1.rb +111 -0
- data/test/rbslapd4.rb +172 -0
- data/test/test_helper.rb +2 -0
- metadata +146 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)),'test_helper')
|
|
2
|
+
require 'ldaptic/schema'
|
|
3
|
+
|
|
4
|
+
class LdapticSyntaxesTest < Test::Unit::TestCase
|
|
5
|
+
NAME_FORM = "(1.2.3 NAME 'foo' DESC ('bar') OC objectClass MUST (cn $ ou) X-AWESOME TRUE)"
|
|
6
|
+
ATTRIBUTE_TYPE = "(1.3.5 NAME 'cn' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15{256}')"
|
|
7
|
+
def assert_parse_error(&block)
|
|
8
|
+
assert_raise(Ldaptic::Schema::ParseError, &block)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def test_name_form
|
|
12
|
+
name_form = Ldaptic::Schema::NameForm.new(NAME_FORM)
|
|
13
|
+
assert_equal "1.2.3", name_form.oid
|
|
14
|
+
assert_equal "foo", name_form.name
|
|
15
|
+
assert_equal %w(foo), name_form.names
|
|
16
|
+
assert_equal %w(cn ou), name_form.must
|
|
17
|
+
assert name_form.x_awesome?
|
|
18
|
+
assert !name_form.x_lame
|
|
19
|
+
assert_raise(NoMethodError) { name_form.applies }
|
|
20
|
+
assert_raise(ArgumentError) { name_form.desc(1) }
|
|
21
|
+
assert_raise(ArgumentError) { name_form.x_lame(1) }
|
|
22
|
+
assert_equal nil, name_form.may
|
|
23
|
+
assert_equal NAME_FORM, name_form.to_s
|
|
24
|
+
assert name_form.inspect.include?("#<Ldaptic::Schema::NameForm")
|
|
25
|
+
assert name_form.inspect.include?("1.2.3")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_object_class
|
|
29
|
+
assert_equal "AUXILIARY", Ldaptic::Schema::ObjectClass.new("(1.2 AUXILIARY)").kind
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_attribute_type
|
|
33
|
+
attribute_type = Ldaptic::Schema::AttributeType.new(ATTRIBUTE_TYPE)
|
|
34
|
+
assert_equal 256, attribute_type.syntax_len
|
|
35
|
+
assert_not_nil attribute_type.syntax
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_parse_error
|
|
39
|
+
assert_parse_error { Ldaptic::Schema::NameForm.new("x") }
|
|
40
|
+
assert_parse_error { Ldaptic::Schema::NameForm.new("(1.2.3 NAME (foo | bar))") }
|
|
41
|
+
assert_parse_error { Ldaptic::Schema::NameForm.new("(1.2.3 &)") }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)),'test_helper')
|
|
2
|
+
require 'ldaptic/syntaxes'
|
|
3
|
+
|
|
4
|
+
class LdapticSyntaxesTest < Test::Unit::TestCase
|
|
5
|
+
|
|
6
|
+
def test_for
|
|
7
|
+
assert_equal Ldaptic::Syntaxes::GeneralizedTime, Ldaptic::Syntaxes.for("Generalized Time")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def test_bit_string
|
|
11
|
+
assert_nil Ldaptic::Syntaxes::BitString.new.error("'01'B")
|
|
12
|
+
assert_not_nil Ldaptic::Syntaxes::BitString.new.error("01'B")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_boolean
|
|
16
|
+
assert_equal true, Ldaptic::Syntaxes::Boolean.parse("TRUE")
|
|
17
|
+
assert_equal false, Ldaptic::Syntaxes::Boolean.parse("FALSE")
|
|
18
|
+
assert_equal "TRUE", Ldaptic::Syntaxes::Boolean.format(true)
|
|
19
|
+
assert_equal "FALSE", Ldaptic::Syntaxes::Boolean.format(false)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_postal_address
|
|
23
|
+
assert_not_nil Ldaptic::Syntaxes::PostalAddress.new.error('\\a')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_generalized_time
|
|
27
|
+
assert_equal Time.utc(2000,1,1,12,34,56), Ldaptic::Syntaxes::GeneralizedTime.parse("20000101123456.0Z")
|
|
28
|
+
assert_equal Time.utc(2000,1,1,12,34,56), Ldaptic::Syntaxes::GeneralizedTime.parse("20000101123456.0Z")
|
|
29
|
+
assert_equal 1601, Ldaptic::Syntaxes::GeneralizedTime.parse("16010101000001.0Z").year
|
|
30
|
+
assert_equal "20000101123456.000000Z", Ldaptic::Syntaxes::GeneralizedTime.format(Time.utc(2000,1,1,12,34,56))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_ia5_string
|
|
34
|
+
assert_nil Ldaptic::Syntaxes::IA5String.new.error('a')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_integer
|
|
38
|
+
assert_equal 1, Ldaptic::Syntaxes::INTEGER.parse("1")
|
|
39
|
+
assert_equal "1", Ldaptic::Syntaxes::INTEGER.format(1)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_printable_string
|
|
43
|
+
assert_nil Ldaptic::Syntaxes::PrintableString.new.error("Az0'\"()+,-./:? =")
|
|
44
|
+
assert_not_nil Ldaptic::Syntaxes::PrintableString.new('$')
|
|
45
|
+
assert_not_nil Ldaptic::Syntaxes::PrintableString.new("\\")
|
|
46
|
+
assert_not_nil Ldaptic::Syntaxes::PrintableString.new("\t")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_country_string
|
|
50
|
+
assert_nil Ldaptic::Syntaxes::CountryString.new.error('ab')
|
|
51
|
+
assert_not_nil Ldaptic::Syntaxes::CountryString.new.error('a')
|
|
52
|
+
assert_not_nil Ldaptic::Syntaxes::CountryString.new.error('abc')
|
|
53
|
+
assert_not_nil Ldaptic::Syntaxes::CountryString.new.error('a_')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_delivery_method
|
|
57
|
+
assert_not_nil Ldaptic::Syntaxes::DeliveryMethod.new.error('')
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_facsimile_telephone_number
|
|
61
|
+
assert_nil Ldaptic::Syntaxes::FacsimileTelephoneNumber.new.error("911")
|
|
62
|
+
assert_nil Ldaptic::Syntaxes::FacsimileTelephoneNumber.new.error("911$b4Length")
|
|
63
|
+
assert_not_nil Ldaptic::Syntaxes::FacsimileTelephoneNumber.new("\t")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__),'..','lib')).uniq!
|
|
2
|
+
require 'ldaptic/adapters'
|
|
3
|
+
require 'ldaptic/adapters/abstract_adapter'
|
|
4
|
+
|
|
5
|
+
class Ldaptic::Adapters::MockAdapter < Ldaptic::Adapters::AbstractAdapter
|
|
6
|
+
register_as(:mock)
|
|
7
|
+
|
|
8
|
+
def schema(arg = nil)
|
|
9
|
+
{
|
|
10
|
+
'objectClasses' => [
|
|
11
|
+
"( 2.5.6.0 NAME 'top' ABSTRACT MUST (objectClass) MAY (cn $ description $ distinguishedName) )",
|
|
12
|
+
"( 2.5.6.6 NAME 'person' SUP top STRUCTURAL MUST (cn) MAY (sn $ age) )",
|
|
13
|
+
"( 0.9.2342.19200300.100.4.19 NAME 'simpleSecurityObject' SUP top AUXILIARY MAY userPassword )",
|
|
14
|
+
"( 9.9.9.1 NAME 'searchResult' SUP top STRUCTURAL MUST (filter $ scope) )"
|
|
15
|
+
],
|
|
16
|
+
'attributeTypes' => [
|
|
17
|
+
"( 2.5.4.0 NAME 'objectClass' SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' NO-USER-MODIFICATION )",
|
|
18
|
+
"( 2.5.4.3 NAME ( 'cn' 'commonName' ) SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
|
19
|
+
"( 2.5.4.49 NAME 'distinguishedName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE NO-USER-MODIFICATION )",
|
|
20
|
+
"( 2.5.4.13 NAME 'description' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )",
|
|
21
|
+
"( 2.5.4.4 NAME ( 'sn' 'surname' ) SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
|
22
|
+
"( 2.5.4.35 NAME 'userPassword' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )",
|
|
23
|
+
"( 2.5.4.4 NAME 'filter' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
|
24
|
+
"( 9.9.9.2 NAME 'scope' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )",
|
|
25
|
+
"( 9.9.9.2 NAME 'age' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )",
|
|
26
|
+
],
|
|
27
|
+
"dITContentRules" => [
|
|
28
|
+
"( 2.5.6.6 NAME 'person' AUX simpleSecurityObject )"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def server_default_base_dn
|
|
34
|
+
"DC=org"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns a mock object which encapsulates the search query.
|
|
38
|
+
def search(options)
|
|
39
|
+
yield({
|
|
40
|
+
'objectClass' => %w(top searchResult),
|
|
41
|
+
'filter' => [options[:filter].to_s],
|
|
42
|
+
'scope' => [options[:scope].to_s],
|
|
43
|
+
'dn' => [options[:base]]
|
|
44
|
+
})
|
|
45
|
+
0
|
|
46
|
+
end
|
|
47
|
+
end
|
data/test/rbslapd1.rb
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
|
2
|
+
|
|
3
|
+
# This is a trivial LDAP server which just stores directory entries in RAM.
|
|
4
|
+
# It does no validation or authentication. This is intended just to
|
|
5
|
+
# demonstrate the API, it's not for real-world use!!
|
|
6
|
+
|
|
7
|
+
$:.unshift('../lib')
|
|
8
|
+
$debug = true
|
|
9
|
+
|
|
10
|
+
require 'ldap/server'
|
|
11
|
+
|
|
12
|
+
# We subclass the Operation class, overriding the methods to do what we need
|
|
13
|
+
|
|
14
|
+
class HashOperation < LDAP::Server::Operation
|
|
15
|
+
def initialize(connection, messageID, hash)
|
|
16
|
+
super(connection, messageID)
|
|
17
|
+
@hash = hash # an object reference to our directory data
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def search(basedn, scope, deref, filter)
|
|
21
|
+
basedn.downcase!
|
|
22
|
+
|
|
23
|
+
case scope
|
|
24
|
+
when LDAP::Server::BaseObject
|
|
25
|
+
# client asked for single object by DN
|
|
26
|
+
obj = @hash[basedn]
|
|
27
|
+
raise LDAP::ResultError::NoSuchObject unless obj
|
|
28
|
+
send_SearchResultEntry(basedn, obj) if LDAP::Server::Filter.run(filter, obj)
|
|
29
|
+
|
|
30
|
+
when LDAP::Server::WholeSubtree
|
|
31
|
+
@hash.each do |dn, av|
|
|
32
|
+
next unless dn.index(basedn, -basedn.length) # under basedn?
|
|
33
|
+
next unless LDAP::Server::Filter.run(filter, av) # attribute filter?
|
|
34
|
+
send_SearchResultEntry(dn, av)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
else
|
|
38
|
+
raise LDAP::ResultError::UnwillingToPerform, "OneLevel not implemented"
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def add(dn, av)
|
|
44
|
+
dn.downcase!
|
|
45
|
+
raise LDAP::ResultError::EntryAlreadyExists if @hash[dn]
|
|
46
|
+
@hash[dn] = av
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def del(dn)
|
|
50
|
+
dn.downcase!
|
|
51
|
+
raise LDAP::ResultError::NoSuchObject unless @hash.has_key?(dn)
|
|
52
|
+
@hash.delete(dn)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def modify(dn, ops)
|
|
56
|
+
entry = @hash[dn]
|
|
57
|
+
raise LDAP::ResultError::NoSuchObject unless entry
|
|
58
|
+
ops.each do |attr, vals|
|
|
59
|
+
op = vals.shift
|
|
60
|
+
case op
|
|
61
|
+
when :add
|
|
62
|
+
entry[attr] ||= []
|
|
63
|
+
entry[attr] += vals
|
|
64
|
+
entry[attr].uniq!
|
|
65
|
+
when :delete
|
|
66
|
+
if vals == []
|
|
67
|
+
entry.delete(attr)
|
|
68
|
+
else
|
|
69
|
+
vals.each { |v| entry[attr].delete(v) }
|
|
70
|
+
end
|
|
71
|
+
when :replace
|
|
72
|
+
entry[attr] = vals
|
|
73
|
+
end
|
|
74
|
+
entry.delete(attr) if entry[attr] == []
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# This is the shared object which carries our actual directory entries.
|
|
80
|
+
# It's just a hash of {dn=>entry}, where each entry is {attr=>[val,val,...]}
|
|
81
|
+
|
|
82
|
+
directory = {}
|
|
83
|
+
|
|
84
|
+
# Let's put some backing store on it
|
|
85
|
+
|
|
86
|
+
require 'yaml'
|
|
87
|
+
begin
|
|
88
|
+
File.open("ldapdb.yaml") { |f| directory = YAML::load(f.read) }
|
|
89
|
+
rescue Errno::ENOENT
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
at_exit do
|
|
93
|
+
File.open("ldapdb.new","w") { |f| f.write(YAML::dump(directory)) }
|
|
94
|
+
File.rename("ldapdb.new","ldapdb.yaml")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Listen for incoming LDAP connections. For each one, create a Connection
|
|
98
|
+
# object, which will invoke a HashOperation object for each request.
|
|
99
|
+
|
|
100
|
+
s = LDAP::Server.new(
|
|
101
|
+
:port => 1389,
|
|
102
|
+
:nodelay => true,
|
|
103
|
+
:listen => 10,
|
|
104
|
+
# :ssl_key_file => "key.pem",
|
|
105
|
+
# :ssl_cert_file => "cert.pem",
|
|
106
|
+
# :ssl_on_connect => true,
|
|
107
|
+
:operation_class => HashOperation,
|
|
108
|
+
:operation_args => [directory]
|
|
109
|
+
)
|
|
110
|
+
s.run_tcpserver
|
|
111
|
+
s.join
|
data/test/rbslapd4.rb
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
|
2
|
+
|
|
3
|
+
# This is similar to rbslapd1.rb but here we use TOMITA Masahiro's prefork
|
|
4
|
+
# library: <http://raa.ruby-lang.org/project/prefork/>
|
|
5
|
+
# Advantages over Ruby threading:
|
|
6
|
+
# - each client connection is handled in its own process; don't need
|
|
7
|
+
# to worry about Ruby thread blocking (except if one client issues
|
|
8
|
+
# overlapping LDAP operations down the same connection, which is uncommon)
|
|
9
|
+
# - better scalability on multi-processor systems
|
|
10
|
+
# - better scalability on single-processor systems (e.g. shouldn't hit
|
|
11
|
+
# max FDs per process limit)
|
|
12
|
+
# Disadvantages:
|
|
13
|
+
# - client connections can't share state in RAM. So our shared directory
|
|
14
|
+
# now has to be read from disk, and flushed to disk after every update.
|
|
15
|
+
#
|
|
16
|
+
# Additionally, I have added schema support. An LDAP v3 client can
|
|
17
|
+
# query the schema remotely, and adds/modifies have data validated.
|
|
18
|
+
|
|
19
|
+
$:.unshift('../lib')
|
|
20
|
+
|
|
21
|
+
require 'ldap/server'
|
|
22
|
+
require 'ldap/server/schema'
|
|
23
|
+
require 'yaml'
|
|
24
|
+
|
|
25
|
+
$debug = nil # $stderr
|
|
26
|
+
|
|
27
|
+
# An object to keep our in-RAM database and synchronise it to disk
|
|
28
|
+
# when necessary
|
|
29
|
+
|
|
30
|
+
class Directory
|
|
31
|
+
attr_reader :data
|
|
32
|
+
|
|
33
|
+
def initialize(filename)
|
|
34
|
+
@filename = filename
|
|
35
|
+
@stat = nil
|
|
36
|
+
update
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# synchronise with directory on disk (re-read if it has changed)
|
|
40
|
+
|
|
41
|
+
def update
|
|
42
|
+
begin
|
|
43
|
+
tmp = {}
|
|
44
|
+
sb = File.stat(@filename)
|
|
45
|
+
return if @stat and @stat.ino == sb.ino and @stat.mtime == sb.mtime
|
|
46
|
+
File.open(@filename) do |f|
|
|
47
|
+
tmp = YAML::load(f.read)
|
|
48
|
+
@stat = f.stat
|
|
49
|
+
end
|
|
50
|
+
rescue Errno::ENOENT
|
|
51
|
+
end
|
|
52
|
+
@data = tmp
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# write back to disk
|
|
56
|
+
|
|
57
|
+
def write
|
|
58
|
+
File.open(@filename+".new","w") { |f| f.write(YAML::dump(@data)) }
|
|
59
|
+
File.rename(@filename+".new",@filename)
|
|
60
|
+
@stat = File.stat(@filename)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# run a block while holding a lock on the database
|
|
64
|
+
|
|
65
|
+
def lock
|
|
66
|
+
File.open(@filename+".lock","w") do |f|
|
|
67
|
+
f.flock(File::LOCK_EX) # will block here until lock available
|
|
68
|
+
yield
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# We subclass the Operation class, overriding the methods to do what we need
|
|
74
|
+
|
|
75
|
+
class DirOperation < LDAP::Server::Operation
|
|
76
|
+
def initialize(connection, messageID, dir)
|
|
77
|
+
super(connection, messageID)
|
|
78
|
+
@dir = dir
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def search(basedn, scope, deref, filter)
|
|
82
|
+
$debug << "Search: basedn=#{basedn.inspect}, scope=#{scope.inspect}, deref=#{deref.inspect}, filter=#{filter.inspect}\n" if $debug
|
|
83
|
+
basedn.downcase!
|
|
84
|
+
|
|
85
|
+
case scope
|
|
86
|
+
when LDAP::Server::BaseObject
|
|
87
|
+
# client asked for single object by DN
|
|
88
|
+
@dir.update
|
|
89
|
+
obj = @dir.data[basedn]
|
|
90
|
+
raise LDAP::ResultError::NoSuchObject unless obj
|
|
91
|
+
ok = LDAP::Server::Filter.run(filter, obj)
|
|
92
|
+
$debug << "Match=#{ok.inspect}: #{obj.inspect}\n" if $debug
|
|
93
|
+
send_SearchResultEntry(basedn, obj) if ok
|
|
94
|
+
|
|
95
|
+
when LDAP::Server::WholeSubtree
|
|
96
|
+
@dir.update
|
|
97
|
+
@dir.data.each do |dn, av|
|
|
98
|
+
$debug << "Considering #{dn}\n" if $debug
|
|
99
|
+
next unless dn.index(basedn, -basedn.length) # under basedn?
|
|
100
|
+
next unless LDAP::Server::Filter.run(filter, av) # attribute filter?
|
|
101
|
+
$debug << "Sending: #{av.inspect}\n" if $debug
|
|
102
|
+
send_SearchResultEntry(dn, av)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
else
|
|
106
|
+
raise LDAP::ResultError::UnwillingToPerform, "OneLevel not implemented"
|
|
107
|
+
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def add(dn, entry)
|
|
112
|
+
entry = @schema.validate(entry)
|
|
113
|
+
entry['createTimestamp'] = [Time.now.gmtime.strftime("%Y%m%d%H%MZ")]
|
|
114
|
+
entry['creatorsName'] = [@connection.binddn.to_s]
|
|
115
|
+
# FIXME: normalize the DN and check it's below our root DN
|
|
116
|
+
# FIXME: validate that a superior object exists
|
|
117
|
+
# FIXME: validate that entry contains the RDN attribute (yuk)
|
|
118
|
+
dn.downcase!
|
|
119
|
+
@dir.lock do
|
|
120
|
+
@dir.update
|
|
121
|
+
raise LDAP::ResultError::EntryAlreadyExists if @dir.data[dn]
|
|
122
|
+
@dir.data[dn] = entry
|
|
123
|
+
@dir.write
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def del(dn)
|
|
128
|
+
dn.downcase!
|
|
129
|
+
@dir.lock do
|
|
130
|
+
@dir.update
|
|
131
|
+
raise LDAP::ResultError::NoSuchObject unless @dir.data.has_key?(dn)
|
|
132
|
+
@dir.data.delete(dn)
|
|
133
|
+
@dir.write
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def modify(dn, ops)
|
|
138
|
+
dn.downcase!
|
|
139
|
+
@dir.lock do
|
|
140
|
+
@dir.update
|
|
141
|
+
entry = @dir.data[dn]
|
|
142
|
+
raise LDAP::ResultError::NoSuchObject unless entry
|
|
143
|
+
entry = @schema.validate(ops, entry) # also does the update
|
|
144
|
+
entry['modifyTimestamp'] = [Time.now.gmtime.strftime("%Y%m%d%H%MZ")]
|
|
145
|
+
entry['modifiersName'] = [@connection.binddn.to_s]
|
|
146
|
+
@dir.data[dn] = entry
|
|
147
|
+
@dir.write
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
directory = Directory.new("ldapdb.yaml")
|
|
153
|
+
|
|
154
|
+
schema = LDAP::Server::Schema.new
|
|
155
|
+
schema.load_system
|
|
156
|
+
schema.load_file("../test/core.schema")
|
|
157
|
+
schema.resolve_oids
|
|
158
|
+
|
|
159
|
+
s = LDAP::Server.new(
|
|
160
|
+
:port => 1389,
|
|
161
|
+
:nodelay => true,
|
|
162
|
+
:listen => 10,
|
|
163
|
+
# :ssl_key_file => "key.pem",
|
|
164
|
+
# :ssl_cert_file => "cert.pem",
|
|
165
|
+
# :ssl_on_connect => true,
|
|
166
|
+
:operation_class => DirOperation,
|
|
167
|
+
:operation_args => [directory],
|
|
168
|
+
:schema => schema,
|
|
169
|
+
:namingContexts => ['dc=example,dc=com']
|
|
170
|
+
)
|
|
171
|
+
s.run_tcpserver
|
|
172
|
+
s.join
|
data/test/test_helper.rb
ADDED