ldumbd 0.1.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/Gemfile +2 -0
- data/LICENSE +19 -0
- data/NEWS +8 -0
- data/README.md +75 -0
- data/Rakefile +25 -0
- data/TODO.org +4 -0
- data/bin/ldumbd +43 -0
- data/config.yml.sample +11 -0
- data/db/migrations/001_create_users.rb +12 -0
- data/db/migrations/002_create_groups.rb +9 -0
- data/db/migrations/003_add_user_group_id.rb +20 -0
- data/db/migrations/004_create_groups_users.rb +10 -0
- data/db/migrations/005_set_uid_and_gid_start_value.rb +59 -0
- data/ldumbd.gemspec +42 -0
- data/lib/ldumbd/filter_converter.rb +190 -0
- data/lib/ldumbd/group.rb +3 -0
- data/lib/ldumbd/ldap_tree.rb +44 -0
- data/lib/ldumbd/operation.rb +61 -0
- data/lib/ldumbd/preforkserver.rb +64 -0
- data/lib/ldumbd/table_map.rb +89 -0
- data/lib/ldumbd/tree_object.rb +67 -0
- data/lib/ldumbd/user.rb +3 -0
- data/lib/ldumbd/version.rb +3 -0
- data/spec/filter_converter_spec.rb +154 -0
- data/spec/operation_spec.rb +274 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/table_map_spec.rb +45 -0
- metadata +209 -0
@@ -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
|
data/lib/ldumbd/user.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|