ldumbd 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,64 @@
1
+ require 'socket'
2
+ require 'etc'
3
+
4
+ module Ldumbd
5
+ class PreforkServer
6
+ def initialize(opt)
7
+ @acceptor = nil
8
+ @bind_address = opt[:bindaddr] || '0.0.0.0'
9
+ @port = opt[:port] || 1389
10
+ @group = opt[:group]
11
+ @user = opt[:user]
12
+ @nodelay = opt.has_key?(:nodelay) ? opt[:nodelay] : true
13
+ @listen = opt[:listen] || 10
14
+ @num_processes = opt[:num_processes] || 10
15
+ @debug = opt[:debug]
16
+ end
17
+
18
+ def run(&block)
19
+ start
20
+ Process::GID.change_privilege(Etc.getgrnam(@group)) if @group
21
+ Process::UID.change_privilege(Etc.getpwnam(@user)) if @user
22
+ @num_processes.times do
23
+ fork do
24
+ trap('INT') { exit }
25
+
26
+ puts "child #$$ accepting connections on #{@bind_address}:#{@port}" if @debug
27
+ loop do
28
+ socket = @acceptor.accept
29
+ block.call(socket)
30
+ socket.close
31
+ end
32
+ exit
33
+ end
34
+ end
35
+ self
36
+ end
37
+
38
+ def join
39
+ Process.waitall
40
+ end
41
+
42
+ private
43
+ def start
44
+ @acceptor = TCPServer.new(@bind_address, @port)
45
+ if @nodelay
46
+ @acceptor.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
47
+ end
48
+ @acceptor.listen(@listen)
49
+ trap('EXIT') { @acceptor.close }
50
+ end
51
+ end
52
+ end
53
+
54
+ module LDAP
55
+ class Server
56
+ def run_preforkserver
57
+ opt = @opt
58
+ server = Ldumbd::PreforkServer.new(opt)
59
+ @thread = server.run do |socket|
60
+ LDAP::Server::Connection::new(socket, opt).handle_requests
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,89 @@
1
+ module Ldumbd
2
+ class TableMap
3
+ TABLE_MAP = {
4
+ group: {
5
+ object_classes: ['posixGroup'],
6
+ attributes: {
7
+ 'gidNumber' => :groups__id,
8
+ 'cn' => :groups__name,
9
+ 'memberUid' => :users
10
+ },
11
+ },
12
+ user: {
13
+ object_classes: ['posixAccount'],
14
+ attributes: {
15
+ 'uidNumber' => :users__id,
16
+ 'uid' => :users__name,
17
+ 'cn' => :users__realname,
18
+ 'loginShell' => :users__shell,
19
+ 'homeDirectory' => :users__homedir,
20
+ 'gidNumber' => :users__group_id
21
+ }
22
+ }
23
+ }
24
+
25
+ def self.invert_attribute_map(attributes)
26
+ attribute_array = attributes.map do |ldap_key, db_key|
27
+ /\A[a-z]*__(?<db_key_compact>.*)\z/ =~ db_key.to_s
28
+ [db_key_compact.nil? ? nil : db_key_compact.to_sym, ldap_key]
29
+ end
30
+ Hash[attribute_array].delete_if { |k, v| k.nil? }
31
+ end
32
+ private_class_method :invert_attribute_map
33
+
34
+ def self.invert_table_map(table_map)
35
+ table_array = table_map.map do |model, properties|
36
+ [model, invert_attribute_map(properties[:attributes])]
37
+ end
38
+ Hash[table_array]
39
+ end
40
+ private_class_method :invert_table_map
41
+
42
+ INVERSE_TABLE_MAP = invert_table_map(TABLE_MAP)
43
+
44
+ def self.db_key(model, ldap_key)
45
+ table = model_to_sym(model)
46
+ TABLE_MAP[table][:attributes][ldap_key]
47
+ end
48
+
49
+ def self.object_classes(model)
50
+ table = model_to_sym(model)
51
+ TABLE_MAP[table][:object_classes]
52
+ end
53
+
54
+ def self.model_to_sym(model)
55
+ model.name.downcase.to_sym
56
+ end
57
+ private_class_method :model_to_sym
58
+
59
+ def self.ldap_keys(model)
60
+ table = model_to_sym(model)
61
+ INVERSE_TABLE_MAP[table]
62
+ end
63
+
64
+ # TODO: move this into User/Group model class
65
+ def self.sequel_to_ldap_object(sequel_object)
66
+ # return unmodified object if it is not a Sequel model instance
67
+ return sequel_object unless sequel_object.is_a?(Sequel::Model)
68
+
69
+ model = sequel_object.class
70
+ ldap_keys = ldap_keys(model)
71
+ ldap_array = sequel_object.values.map do |sequel_key, value|
72
+ # LDAP::Server expects all values to be Arrays
73
+ [ldap_keys[sequel_key], [value]]
74
+ end
75
+ ldap_object = Hash[ldap_array]
76
+
77
+ ldap_object['objectClass'] = object_classes(model)
78
+ if sequel_object.is_a?(Group)
79
+ ldap_object['dn_prefix'] = "cn=#{sequel_object.name},ou=Groups"
80
+ if sequel_object.users.any?
81
+ ldap_object['memberUid'] = sequel_object.users.map { |u| u.name }
82
+ end
83
+ elsif sequel_object.is_a?(User)
84
+ ldap_object['dn_prefix'] = "uid=#{sequel_object.name},ou=People"
85
+ end
86
+ ldap_object
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,67 @@
1
+ require 'ldap/server'
2
+ require 'sequel'
3
+ require 'ldumbd/filter_converter'
4
+
5
+ module Ldumbd
6
+ class TreeObject
7
+ attr_accessor :children
8
+ attr_accessor :attributes
9
+
10
+ def initialize(attributes = {})
11
+ @attributes = attributes
12
+ @children = []
13
+ end
14
+
15
+ def matches_filter?(filter)
16
+ ldap_object = TableMap.sequel_to_ldap_object(@attributes)
17
+ LDAP::Server::Filter.run(filter, ldap_object)
18
+ end
19
+
20
+ def each_child(filter = [:true], recurse = false, &block)
21
+ @children.each do |child|
22
+ if child.is_a?(TreeObject)
23
+ if LDAP::Server::Filter.run(filter, child.attributes)
24
+ yield child.attributes
25
+ end
26
+ child.each_child(filter, recurse, &block) if recurse
27
+ elsif child < Sequel::Model
28
+ query = Ldumbd::FilterConverter.filter_to_sequel(child, filter)
29
+ child.where(query).each do |r|
30
+ yield r
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def self.by_dn(basedn, dn)
37
+ object = nil
38
+
39
+ dn_filter, model = dn_to_filter_and_model(basedn, dn)
40
+ if dn_filter
41
+ query = Ldumbd::FilterConverter.filter_to_sequel(model, dn_filter)
42
+ result = model.where(query).first
43
+ object = result ? self.new(result) : nil
44
+ end
45
+ object
46
+ end
47
+
48
+ def self.attribute_from_dn(basedn, dn, attribute, ou)
49
+ dn.scan(/\A#{attribute}=([^,]*),ou=#{ou},#{basedn}\z/).flatten.last
50
+ end
51
+ private_class_method :attribute_from_dn
52
+
53
+ def self.dn_to_filter_and_model(basedn, dn)
54
+ uid = attribute_from_dn(basedn, dn, 'uid', 'People')
55
+ cn = attribute_from_dn(basedn, dn, 'cn', 'Groups')
56
+
57
+ if uid
58
+ return [:eq, 'uid', nil, uid], User
59
+ elsif cn
60
+ return [:eq, 'cn', nil, cn], Group
61
+ else
62
+ return nil, nil
63
+ end
64
+ end
65
+ private_class_method :dn_to_filter_and_model
66
+ end
67
+ end
@@ -0,0 +1,3 @@
1
+ class User < Sequel::Model
2
+ many_to_many :groups
3
+ end
@@ -0,0 +1,3 @@
1
+ module Ldumbd
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,154 @@
1
+ require_relative 'spec_helper'
2
+
3
+ require 'ldumbd/filter_converter'
4
+
5
+ describe Ldumbd::FilterConverter do
6
+ before(:each) do
7
+ @john = new_user('John Doe')
8
+ @jane = new_user('Jane Doe')
9
+ @all_group = Group.create(name: 'allusers')
10
+ @all_group.add_user(@john)
11
+ @all_group.add_user(@jane)
12
+ end
13
+
14
+ it 'should process :true filters' do
15
+ filter = [:true]
16
+ user_query = Ldumbd::FilterConverter.filter_to_sequel(User, filter)
17
+ group_query = Ldumbd::FilterConverter.filter_to_sequel(Group, filter)
18
+ users = User.where(user_query)
19
+ groups = Group.where(group_query)
20
+ users.count.must_equal User.count
21
+ groups.count.must_equal Group.count
22
+ end
23
+
24
+ it 'should process :false filters' do
25
+ filter = [:false]
26
+ user_query = Ldumbd::FilterConverter.filter_to_sequel(User, filter)
27
+ group_query = Ldumbd::FilterConverter.filter_to_sequel(Group, filter)
28
+ users = User.where(user_query)
29
+ groups = Group.where(group_query)
30
+ users.count.must_equal 0
31
+ groups.count.must_equal 0
32
+ end
33
+
34
+ it 'should process :eq filters' do
35
+ filter = [:eq, 'uid', nil, @john.name]
36
+ query = Ldumbd::FilterConverter.filter_to_sequel(User, filter)
37
+ users = User.where(query)
38
+ users.count.must_equal 1
39
+ users.first.must_equal @john
40
+ end
41
+
42
+ it 'should process :or filters' do
43
+ filter = [:or,
44
+ [:eq, 'uid', nil, @john.name],
45
+ [:eq, 'uid', nil, @jane.name]]
46
+ query = Ldumbd::FilterConverter.filter_to_sequel(User, filter)
47
+ users = User.where(query).order(:id)
48
+ users.count.must_equal 2
49
+ users.all[0].must_equal @john
50
+ users.all[1].must_equal @jane
51
+ end
52
+
53
+ it 'should process memberUid filters' do
54
+ filter = [:eq, 'memberUid', nil, @jane.name]
55
+ query = Ldumbd::FilterConverter.filter_to_sequel(Group, filter)
56
+ groups = Group.where(query)
57
+ groups.count.must_equal 1
58
+ groups.first.must_equal @all_group
59
+ end
60
+
61
+ it 'should process :and filters' do
62
+ filter = [:and,
63
+ [:eq, 'gidNumber', nil, @all_group.id],
64
+ [:eq, 'memberUid', nil, @jane.name],
65
+ [:not, [:eq, 'memberUid', nil, 'nonexistentusername42']]]
66
+ query = Ldumbd::FilterConverter.filter_to_sequel(Group, filter)
67
+ groups = Group.where(query)
68
+ groups.count.must_equal 1
69
+ groups.first.must_equal @all_group
70
+ end
71
+
72
+ it 'should process :ge and :le filters' do
73
+ filter = [:and,
74
+ [:ge, 'gidNumber', nil, 65000],
75
+ [:le, 'gidNumber', nil, 66000]]
76
+ query = Ldumbd::FilterConverter.filter_to_sequel(User, filter)
77
+ users = User.where(query).order(:id)
78
+ users.count.must_equal 2
79
+ users.all[0].must_equal @john
80
+ users.all[1].must_equal @jane
81
+ end
82
+
83
+ it 'should process :substrings filters' do
84
+ filter = [:substrings, 'uid', nil, "jo", "doe"]
85
+ query = Ldumbd::FilterConverter.filter_to_sequel(User, filter)
86
+ users = User.where(query)
87
+ users.count.must_equal 1
88
+ users.first.must_equal @john
89
+ end
90
+
91
+ it 'should process :substrings filters on memberUids' do
92
+ filter = [:substrings, 'memberUid', nil, "jo", "doe"]
93
+ query = Ldumbd::FilterConverter.filter_to_sequel(Group, filter)
94
+ groups = Group.where(query)
95
+ groups.count.must_equal 1
96
+ groups.first.must_equal @all_group
97
+ end
98
+
99
+ it 'should return no results for unknown attributes' do
100
+ filter = [:eq, 'nonexistent', nil, 'somethingsomething']
101
+ query = Ldumbd::FilterConverter.filter_to_sequel(Group, filter)
102
+ groups = Group.where(query)
103
+ groups.count.must_equal 0
104
+ end
105
+
106
+ it 'should process objectClass :substrings requests' do
107
+ # (objectClass=posixGr*)
108
+ filter = [:substrings, 'objectClass', nil, 'posixGr', nil]
109
+ query = Ldumbd::FilterConverter.filter_to_sequel(Group, filter)
110
+ groups = Group.where(query)
111
+ groups.count.must_equal Group.count
112
+ end
113
+
114
+ it 'should process objectClass :eq requests' do
115
+ # (objectClass=posixGroup)
116
+ filter = [:eq, 'objectClass', nil, 'posixGroup']
117
+ query = Ldumbd::FilterConverter.filter_to_sequel(Group, filter)
118
+ groups = Group.where(query)
119
+ groups.count.must_equal Group.count
120
+ end
121
+
122
+ it 'should process objectClass :le requests' do
123
+ # (objectClass<=posixGroupZZZ)
124
+ filter = [:le, 'objectClass', nil, 'posixGroupZZZ']
125
+ query = Ldumbd::FilterConverter.filter_to_sequel(Group, filter)
126
+ groups = Group.where(query)
127
+ groups.count.must_equal Group.count
128
+ end
129
+
130
+ it 'should process objectClass :ge requests' do
131
+ # (objectClass>=posixGroupGaaah)
132
+ filter = [:ge, 'objectClass', nil, 'posixGaaah']
133
+ query = Ldumbd::FilterConverter.filter_to_sequel(Group, filter)
134
+ groups = Group.where(query)
135
+ groups.count.must_equal Group.count
136
+ end
137
+
138
+ it 'should process :present filters' do
139
+ # (homeDirectory=*)
140
+ filter = [:present, 'homeDirectory']
141
+ query = Ldumbd::FilterConverter.filter_to_sequel(User, filter)
142
+ users = User.where(query)
143
+ users.count.must_equal User.count
144
+ end
145
+
146
+ it 'should process :present filters on memberUids' do
147
+ # (memberUid=*)
148
+ filter = [:present, 'memberUid']
149
+ query = Ldumbd::FilterConverter.filter_to_sequel(Group, filter)
150
+ groups = Group.where(query)
151
+ groups_with_members = Group.where(users: User.all)
152
+ groups.count.must_equal groups_with_members.count
153
+ end
154
+ end
@@ -0,0 +1,274 @@
1
+ require_relative 'spec_helper'
2
+
3
+ require 'ldumbd/table_map'
4
+ require 'ldumbd/ldap_tree'
5
+ require 'ldumbd/operation'
6
+
7
+ class Ldumbd::Operation
8
+ public :search_results
9
+ end
10
+
11
+ class ConnectionMock
12
+ def opt
13
+ Hash.new
14
+ end
15
+ end
16
+
17
+ class MessageIDMock; end
18
+
19
+ def ldap_objects(sequel_objects)
20
+ sequel_objects.map { |o| Ldumbd::TableMap.sequel_to_ldap_object(o) }
21
+ end
22
+
23
+ describe Ldumbd::Operation do
24
+ before(:each) do
25
+ @john = new_user('John Doe')
26
+ @jane = new_user('Jane Doe')
27
+ @no_group = Group.where(id: 65534, name: 'nogroup').first
28
+ @all_group = Group.create(name: 'allusers')
29
+ @all_group.add_user(@john)
30
+ @all_group.add_user(@jane)
31
+
32
+ @basedn = 'dc=example,dc=org'
33
+ @root = {
34
+ 'dn_prefix' => '',
35
+ 'dc' => 'example',
36
+ 'objectClass' => %w{top domain}
37
+ }
38
+ @people = {
39
+ 'dn_prefix' => 'ou=People',
40
+ 'ou' => 'People',
41
+ 'objectClass' => %w{top organizationalUnit}
42
+ }
43
+ @groups = {
44
+ 'dn_prefix' => 'ou=Groups',
45
+ 'ou' => 'Groups',
46
+ 'objectClass' => %w{top organizationalUnit}
47
+ }
48
+ @operation = Ldumbd::Operation.new(ConnectionMock.new,
49
+ MessageIDMock.new,
50
+ Ldumbd::LdapTree.new(@basedn))
51
+ end
52
+
53
+ it 'should raise exceptions when using an invalid search scope' do
54
+ proc do
55
+ @operation.search_results(nil, 'InvalidScope', nil, nil)
56
+ end.must_raise LDAP::ResultError::UnwillingToPerform
57
+ end
58
+
59
+ describe LDAP::Server::BaseObject do
60
+ before(:each) do
61
+ @scope = LDAP::Server::BaseObject
62
+ end
63
+
64
+ it 'should find users by dn' do
65
+ filter = [:true]
66
+ dn = "uid=#{@john.name},ou=People,#{@basedn}"
67
+ results = []
68
+ @operation.search_results(dn, @scope, nil, filter) do |r|
69
+ results << r
70
+ end
71
+ results.must_equal ldap_objects([@john])
72
+ end
73
+
74
+ it 'should find organization units by dn' do
75
+ filter = [:true]
76
+ dn = "ou=People,#{@basedn}"
77
+ results = []
78
+ @operation.search_results(dn, @scope, nil, filter) do |r|
79
+ results << r
80
+ end
81
+ results.must_equal [@people]
82
+ end
83
+
84
+ it 'should find the root object by dn' do
85
+ filter = [:true]
86
+ dn = @basedn
87
+ results = []
88
+ @operation.search_results(dn, @scope, nil, filter) do |r|
89
+ results << r
90
+ end
91
+ results.must_equal [@root]
92
+ end
93
+
94
+ it 'should raise exceptions when searching for nonexistent objects' do
95
+ filter = [:true]
96
+ ["dc=non,dc=existent",
97
+ "uid=nonexistent,ou=People,#{@basedn}"].each do |dn|
98
+ proc do
99
+ @operation.search_results(dn, @scope, nil, filter)
100
+ end.must_raise LDAP::ResultError::NoSuchObject
101
+ end
102
+ end
103
+
104
+ it 'should not return any objects when using unsatisfiable filters' do
105
+ filter = [:eq, 'nonexistent_attribute', nil, 'nonexistent_value']
106
+ dn = @basedn
107
+ results = []
108
+ @operation.search_results(dn, @scope, nil, filter) do |r|
109
+ results << r
110
+ end
111
+ results.must_equal []
112
+ end
113
+ end
114
+
115
+ describe LDAP::Server::SingleLevel do
116
+ before(:each) do
117
+ @scope = LDAP::Server::SingleLevel
118
+ end
119
+
120
+ it 'should retrieve the immediate children of an object' do
121
+ filter = [:true]
122
+ dn = @basedn
123
+ results = []
124
+ @operation.search_results(dn, @scope, nil, filter) do |r|
125
+ results << r
126
+ end
127
+ subtree = [@people, @groups]
128
+ results.must_equal_unordered subtree
129
+ end
130
+
131
+ it 'should retrieve all users via the People ou' do
132
+ filter = [:true]
133
+ dn = "ou=People,#{@basedn}"
134
+ results = []
135
+ @operation.search_results(dn, @scope, nil, filter) do |r|
136
+ results << r
137
+ end
138
+ people_tree = [@john, @jane]
139
+ results.must_equal_unordered ldap_objects(people_tree)
140
+ end
141
+
142
+ it 'should retrieve all users via the People ou when using a filter' do
143
+ filter = [:eq, 'objectClass', nil, 'posixAccount']
144
+ dn = "ou=People,#{@basedn}"
145
+ results = []
146
+ @operation.search_results(dn, @scope, nil, filter) do |r|
147
+ results << r
148
+ end
149
+ people_tree = [@john, @jane]
150
+ results.must_equal_unordered ldap_objects(people_tree)
151
+ end
152
+
153
+ it 'should raise exceptions when searching for nonexistent objects' do
154
+ filter = [:true]
155
+ ["dc=non,dc=existent",
156
+ "uid=nonexistent,ou=People,#{@basedn}"].each do |dn|
157
+ proc do
158
+ @operation.search_results(dn, @scope, nil, filter)
159
+ end.must_raise LDAP::ResultError::NoSuchObject
160
+ end
161
+ end
162
+
163
+ it 'should not return any objects when using unsatisfiable filters' do
164
+ filter = [:eq, 'nonexistent_attribute', nil, 'nonexistent_value']
165
+ dn = @basedn
166
+ results = []
167
+ @operation.search_results(dn, @scope, nil, filter) do |r|
168
+ results << r
169
+ end
170
+ results.must_equal []
171
+ end
172
+ end
173
+
174
+ describe LDAP::Server::WholeSubtree do
175
+ before(:each) do
176
+ @scope = LDAP::Server::WholeSubtree
177
+ end
178
+
179
+ it 'should retrieve the whole tree via the basedn' do
180
+ filter = [:true]
181
+ dn = @basedn
182
+ results = []
183
+ @operation.search_results(dn, @scope, nil, filter) do |r|
184
+ results << r
185
+ end
186
+ ldap_tree = [@root, @people, @john, @jane, @groups, @no_group, @all_group]
187
+ results.must_equal_unordered ldap_objects(ldap_tree)
188
+ end
189
+
190
+ it 'should include the base object in the results when using filters' do
191
+ filter = [:eq, 'uid', nil, @john.name]
192
+ dn = "uid=#{@john.name},ou=People,#{@basedn}"
193
+ results = []
194
+ @operation.search_results(dn, @scope, nil, filter) do |r|
195
+ results << r
196
+ end
197
+ results.must_equal ldap_objects([@john])
198
+ end
199
+
200
+ it 'should retrieve all users via the People ou' do
201
+ filter = [:true]
202
+ dn = "ou=People,#{@basedn}"
203
+ results = []
204
+ @operation.search_results(dn, @scope, nil, filter) do |r|
205
+ results << r
206
+ end
207
+ people_tree = [@people, @john, @jane]
208
+ results.must_equal_unordered ldap_objects(people_tree)
209
+ end
210
+
211
+ it 'should retrieve all users via the People ou when using a filter' do
212
+ filter = [:eq, 'objectClass', nil, 'posixAccount']
213
+ dn = "ou=People,#{@basedn}"
214
+ results = []
215
+ @operation.search_results(dn, @scope, nil, filter) do |r|
216
+ results << r
217
+ end
218
+ people_tree = [@john, @jane]
219
+ results.must_equal_unordered ldap_objects(people_tree)
220
+ end
221
+
222
+ it 'should retrieve single users by dn' do
223
+ filter = [:true]
224
+ dn = "uid=#{@john.name},ou=People,#{@basedn}"
225
+ results = []
226
+ @operation.search_results(dn, @scope, nil, filter) do |r|
227
+ results << r
228
+ end
229
+ results.must_equal ldap_objects([@john])
230
+ end
231
+
232
+ it 'should raise exceptions when searching for nonexistent objects' do
233
+ filter = [:true]
234
+ ["dc=non,dc=existent",
235
+ "uid=nonexistent,ou=People,#{@basedn}"].each do |dn|
236
+ proc do
237
+ @operation.search_results(dn, @scope, nil, filter)
238
+ end.must_raise LDAP::ResultError::NoSuchObject
239
+ end
240
+ end
241
+
242
+ it 'should not return any objects when using unsatisfiable filters' do
243
+ filter = [:eq, 'nonexistent_attribute', nil, 'nonexistent_value']
244
+ dn = @basedn
245
+ results = []
246
+ @operation.search_results(dn, @scope, nil, filter) do |r|
247
+ results << r
248
+ end
249
+ results.must_equal []
250
+ end
251
+ end
252
+
253
+ describe 'search' do
254
+ it 'should call send_SearchResultEntry' do
255
+ filter = [:true]
256
+ scope = LDAP::Server::BaseObject
257
+ mock = MiniTest::Mock.new
258
+
259
+ root_dn = @basedn
260
+ root_avs = @root.reject { |k, v| k == 'dn_prefix' }
261
+ john_dn = "uid=#{@john.name},ou=People,#{@basedn}"
262
+ john_attrs = Ldumbd::TableMap.sequel_to_ldap_object(@john)
263
+ john_avs = john_attrs.reject { |k, v| k == 'dn_prefix' }
264
+
265
+ mock.expect(:send_SearchResultEntry, nil, [root_dn, root_avs])
266
+ mock.expect(:send_SearchResultEntry, nil, [john_dn, john_avs])
267
+ @operation.stub(:send_SearchResultEntry,
268
+ ->(dn, avs) { mock.send_SearchResultEntry(dn, avs) }) do
269
+ @operation.search(root_dn, scope, nil, filter)
270
+ @operation.search(john_dn, scope, nil, filter)
271
+ end
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,41 @@
1
+ require 'minitest/autorun'
2
+ require 'yaml'
3
+ require 'sequel'
4
+
5
+ DATABASE_URL = ENV['DATABASE_URL'] || 'sqlite://ldumbd-test.sqlite3'
6
+ DB = Sequel.connect(DATABASE_URL)
7
+
8
+ require 'ldumbd/user'
9
+ require 'ldumbd/group'
10
+
11
+ class MiniTest::Spec
12
+ before :each do
13
+ @db_transaction = Fiber.new do
14
+ DB.transaction(savepoint: true, rollback: :always) { Fiber.yield }
15
+ end
16
+ @db_transaction.resume
17
+ end
18
+
19
+ after :each do
20
+ @db_transaction.resume
21
+ end
22
+ end
23
+
24
+ def new_user(realname)
25
+ name = realname.downcase.gsub(' ', '')
26
+ homedir = "/home/#{name}"
27
+ User.create(name: name,
28
+ homedir: homedir,
29
+ realname: realname)
30
+ end
31
+
32
+ module MiniTest::Assertions
33
+ def assert_equal_unordered(a, b, msg = nil)
34
+ msg ||= "Expected #{mu_pp(a)} to be equivalent to #{mu_pp(b)}"
35
+ freq_a = a.inject(Hash.new(0)) { |h, v| h[v] += 1; h }
36
+ freq_b = b.inject(Hash.new(0)) { |h, v| h[v] += 1; h }
37
+ assert(freq_a == freq_b, msg)
38
+ end
39
+ end
40
+
41
+ Array.infect_an_assertion :assert_equal_unordered, :must_equal_unordered