ldaptic 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/README.rdoc +7 -16
- data/lib/ldaptic/adapters/active_directory_adapter.rb +9 -5
- data/lib/ldaptic/adapters/active_directory_ext.rb +2 -0
- data/lib/ldaptic/adapters/ldap_conn_adapter.rb +0 -6
- data/lib/ldaptic/attribute_set.rb +52 -17
- data/lib/ldaptic/dn.rb +10 -6
- data/lib/ldaptic/entry.rb +46 -17
- data/lib/ldaptic/filter.rb +1 -1
- data/lib/ldaptic/matching_rules.rb +80 -0
- data/lib/ldaptic/methods.rb +10 -7
- data/lib/ldaptic/schema.rb +6 -0
- data/test/ldaptic_adapters_test.rb +1 -1
- data/test/ldaptic_attribute_set_test.rb +1 -0
- data/test/ldaptic_dn_test.rb +5 -0
- data/test/ldaptic_hierarchy_test.rb +1 -0
- data/test/ldaptic_matching_rules_test.rb +38 -0
- metadata +11 -8
- data/lib/ldaptic/active_model.rb +0 -37
- data/lib/ldaptic/railtie.rb +0 -9
- data/test/core.schema +0 -582
- data/test/rbslapd1.rb +0 -111
- data/test/rbslapd4.rb +0 -172
data/test/rbslapd1.rb
DELETED
@@ -1,111 +0,0 @@
|
|
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
DELETED
@@ -1,172 +0,0 @@
|
|
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
|