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 ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2014 Sebastian Boehm
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/NEWS ADDED
@@ -0,0 +1,8 @@
1
+ * ldumbd NEWS
2
+ ** 0.1.0 (2014-03-21)
3
+
4
+ - First release
5
+
6
+ # Local Variables:
7
+ # mode: org
8
+ # End:
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ ldumbd
2
+ ======
3
+
4
+ A simple, self-contained LDAP server with a database back end.
5
+
6
+ Documentation
7
+ -------------
8
+
9
+ Ldumbd is a simple, self-contained read-only LDAP server that uses PostgreSQL, MySQL/MariaDB or SQLite as a back end.
10
+
11
+ Ldumbd is designed primarily to act as an LDAP gateway to a simple SQL user database for use with the `nss-pam-ldapd` Name Service Switch (NSS) module.
12
+
13
+ Limitations
14
+ -----------
15
+
16
+ At the moment, ldumbd has no support for any of the following:
17
+
18
+ - LDAP schemas
19
+ - LDAP binds
20
+ - any request type other than search requests
21
+ - "approximately equal" operators in search filters
22
+
23
+ Installation: Debian Wheezy
24
+ ---------------------------
25
+
26
+ sudo -i
27
+ export LDUMBD_DIR=/var/lib/ldumbd
28
+ mkdir -p ${LDUMBD_DIR}
29
+ groupadd -r ldumbd
30
+ useradd -r -s /bin/false -g ldumbd -d ${LDUMBD_DIR} ldumbd
31
+ chown ldumbd:ldumbd ${LDUMBD_DIR}
32
+ chmod 700 ${LDUMBD_DIR}
33
+ gem install ldumbd
34
+ export MIGRATIONS=$(dirname $(gem contents ldumbd | grep migrations/001))
35
+
36
+ Database setup: SQLite
37
+ ----------------------
38
+
39
+ aptitude install libsqlite3-dev
40
+ gem install sqlite3
41
+ sudo -u ldumbd sequel -m ${MIGRATIONS} sqlite://${LDUMBD_DIR}/ldumbd.sqlite3
42
+
43
+ Database setup: PostgreSQL
44
+ --------------------------
45
+
46
+ aptitude install postgresql libpq-dev
47
+ sudo -u postgres createuser ldumbd
48
+ sudo -u postgres createdb -O ldumbd ldumbd
49
+ gem install pg
50
+ sudo -u ldumbd sequel -m ${MIGRATIONS} postgres:///ldumbd
51
+
52
+ Database setup: MySQL/MariaDB
53
+ -----------------------------
54
+
55
+ export DB_PASSWORD='secret'
56
+ aptitude install mysql-server libmysqlclient-dev
57
+ cat <<EOS | mysql -u root -p
58
+ CREATE DATABASE ldumbd;
59
+ CREATE USER 'ldumbd'@'localhost' IDENTIFIED BY 'secret';
60
+ GRANT ALL PRIVILEGES ON ldumbd.* TO 'ldumbd'@'localhost';
61
+ EOS
62
+ gem install mysql2
63
+ sequel -m ${MIGRATIONS} "mysql2://ldumbd:${DB_PASSWORD}@localhost/ldumbd"
64
+
65
+ Running ldumbd
66
+ --------------
67
+
68
+ cp ${LDUMBD_DIR}/config.yml.sample /etc/ldumbd.yml
69
+ $EDITOR /etc/ldumbd.yml
70
+ ldumbd /etc/ldumbd.yml
71
+
72
+ Copyright
73
+ ---------
74
+
75
+ Copyright (c) 2014 Sebastian Boehm. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ require 'rake/testtask'
2
+
3
+ MIGRATIONS = 'db/migrations'
4
+ DATABASE_URL = ENV['DATABASE_URL'] || 'sqlite://ldumbd-test.sqlite3'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.pattern = 'spec/**/*_spec.rb'
8
+ end
9
+
10
+ namespace :db do
11
+ desc 'Run database migrations'
12
+ task :migrate do
13
+ sh "bundle exec sequel -m #{MIGRATIONS} #{DATABASE_URL}"
14
+ end
15
+
16
+ desc 'Drop all tables'
17
+ task :nuke do
18
+ nuke_all_tables = 'DB.tables.each {|t| DB.drop_table?(t) }'
19
+ sh "bundle exec sequel -m #{MIGRATIONS} -M 0 #{DATABASE_URL}"
20
+ sh "bundle exec sequel -c '#{nuke_all_tables}' #{DATABASE_URL}"
21
+ end
22
+
23
+ desc 'Reset database'
24
+ task :reset => [:nuke, :migrate]
25
+ end
data/TODO.org ADDED
@@ -0,0 +1,4 @@
1
+ * Filters
2
+ - add documentation for missing :approx filter
3
+
4
+ * Add some simple server specs using LDAP client library
data/bin/ldumbd ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'sequel'
4
+ require 'yaml'
5
+
6
+ require 'ldumbd/operation'
7
+ require 'ldumbd/ldap_tree'
8
+ require 'ldumbd/preforkserver'
9
+
10
+ if ARGV.size != 1
11
+ puts 'usage: ldumbd LDUMBD_CONFIG'
12
+ exit false
13
+ end
14
+
15
+ begin
16
+ debug = ENV['DEBUG'] == '1'
17
+ config = YAML.load(File.read(ARGV[0]))
18
+ DB = Sequel.connect(config['database'])
19
+ require 'ldumbd/user'
20
+ require 'ldumbd/group'
21
+ DB.disconnect
22
+
23
+ ldap_tree = Ldumbd::LdapTree.new(config['basedn'])
24
+
25
+ puts "ldumbd #{Ldumbd::VERSION} starting..."
26
+ server = LDAP::Server.new(bindaddr: config['bind_address'],
27
+ port: config['port'],
28
+ nodelay: true,
29
+ listen: 10,
30
+ num_processes: 10,
31
+ operation_class: Ldumbd::Operation,
32
+ operation_args: ldap_tree,
33
+ user: config['user'],
34
+ group: config['group'],
35
+ debug: debug)
36
+ server.run_preforkserver
37
+ trap('INT') { puts 'caught SIGINT, shutting down'; exit }
38
+ puts "prefork ok, resuming normal operation"
39
+ server.join
40
+ rescue => e
41
+ $stderr.puts e.message
42
+ $stderr.puts e.backtrace if debug
43
+ end
data/config.yml.sample ADDED
@@ -0,0 +1,11 @@
1
+ #bind_address: 0.0.0.0
2
+ #port: 1389
3
+ #user: ldumbd
4
+ #group: ldumbd
5
+ basedn: dc=example,dc=org
6
+
7
+ # database: postgres:///ldumbd
8
+
9
+ # database: postgres://user:password@host/database
10
+ # database: mysql2://user:password@host:port/database
11
+ # database: sqlite:///path/to/database.sqlite3
@@ -0,0 +1,12 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table(:users) do
4
+ primary_key :id
5
+ String :name, size: 32, null: false, unique: true
6
+ String :realname, size: 64, null: false, default: ''
7
+ String :shell, size: 32, null: false, default: '/bin/false'
8
+ String :homedir, size: 128, null: false
9
+ index :name
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table(:groups) do
4
+ primary_key :id
5
+ String :name, size: 32, null: false, unique: true
6
+ index :name
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,20 @@
1
+ Sequel.migration do
2
+ nogroup_gid = 65534
3
+
4
+ up do
5
+ self[:groups].insert(id: nogroup_gid, name: 'nogroup')
6
+ alter_table(:users) do
7
+ add_foreign_key :group_id, :groups, null: false, default: nogroup_gid
8
+ # MySQL automatically adds indexes for foreign key columns
9
+ add_index :group_id unless DB.database_type == :mysql
10
+ end
11
+ end
12
+
13
+ down do
14
+ alter_table(:users) do
15
+ drop_index :group_id unless DB.database_type == :mysql
16
+ drop_foreign_key :group_id
17
+ end
18
+ self[:groups].where(id: nogroup_gid).delete
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table(:groups_users) do
4
+ foreign_key :group_id, :groups, null: false
5
+ foreign_key :user_id, :users, null: false
6
+ primary_key [:group_id, :user_id]
7
+ index :user_id
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,59 @@
1
+ def set_min_max_value_commands(dbtype, min, max)
2
+ case dbtype
3
+ when :postgres
4
+ options = "start #{min} restart #{min} minvalue #{min} maxvalue #{max}"
5
+ ["alter sequence users_id_seq #{options};",
6
+ "alter sequence groups_id_seq #{options};"]
7
+ when :sqlite
8
+ id = min-1
9
+ name = '#dummyname#'
10
+ dir = '#dummydir#'
11
+ ["insert into users(id, name, homedir) values(#{id}, '#{name}', '#{dir}');",
12
+ "delete from users where id = #{id};",
13
+ "insert into groups(id, name) values(#{id}, '#{name}');",
14
+ "delete from groups where id = #{id};"]
15
+ when :mysql
16
+ ["alter table users auto_increment = #{min};",
17
+ "alter table groups auto_increment = #{min};"]
18
+ else
19
+ raise Sequel::Error, "database type '#{dbtype}' not supported"
20
+ end
21
+ end
22
+
23
+ def reset_min_max_value_commands(dbtype, next_uid, next_gid)
24
+ case dbtype
25
+ when :postgres
26
+ ["alter sequence users_id_seq start #{next_uid} restart #{next_uid} \
27
+ no minvalue no maxvalue;",
28
+ "alter sequence groups_id_seq start #{next_gid} restart #{next_gid} \
29
+ no minvalue no maxvalue;"]
30
+ when :sqlite
31
+ ["delete from sqlite_sequence where name = 'users';",
32
+ "delete from sqlite_sequence where name = 'groups';"]
33
+ when :mysql
34
+ ["alter table users auto_increment = #{next_uid};",
35
+ "alter table groups auto_increment = #{next_gid};"]
36
+ else
37
+ raise Sequel::Error, "database type '#{dbtype}' not supported"
38
+ end
39
+ end
40
+
41
+ Sequel.migration do
42
+ dbtype = DB.database_type
43
+ min_id = 100_000
44
+ max_id = 200_000
45
+
46
+ up do
47
+ set_min_max_value_commands(dbtype, min_id, max_id).each do |command|
48
+ run command
49
+ end
50
+ end
51
+
52
+ down do
53
+ next_uid = self[:users].max(:id).to_i + 1
54
+ next_gid = self[:groups].max(:id).to_i + 1
55
+ reset_min_max_value_commands(dbtype, next_uid, next_gid).each do |command|
56
+ run command
57
+ end
58
+ end
59
+ end
data/ldumbd.gemspec ADDED
@@ -0,0 +1,42 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'ldumbd/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'ldumbd'
8
+ spec.version = Ldumbd::VERSION
9
+ spec.author = 'Sebastian Boehm'
10
+ spec.email = 'sebastian@sometimesfood.org'
11
+
12
+ spec.summary = %q{A simple, self-contained LDAP server}
13
+ spec.homepage = 'https://github.com/sometimesfood/ldumbd'
14
+ spec.license = 'MIT'
15
+ spec.description = <<EOS
16
+ Ldumbd is a simple, self-contained read-only LDAP server that uses
17
+ PostgreSQL, MySQL/MariaDB or SQLite as a back end.
18
+ EOS
19
+
20
+ spec.files = `git ls-files -z`.split("\0") &
21
+ Dir['config.yml.sample',
22
+ 'Gemfile',
23
+ 'ldumbd.gemspec',
24
+ 'LICENSE',
25
+ 'NEWS',
26
+ 'Rakefile',
27
+ 'README.md',
28
+ 'TODO.org',
29
+ '{bin,lib,db,spec}/**/*']
30
+ spec.executables = ['ldumbd']
31
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
32
+
33
+ spec.add_dependency 'ruby-ldapserver', '~> 0.5.0'
34
+ spec.add_dependency 'sequel', '~> 4.7.0'
35
+
36
+ spec.add_development_dependency 'sqlite3', '~> 1.3.9'
37
+ spec.add_development_dependency 'pg', '~> 0.17.1'
38
+ spec.add_development_dependency 'mysql2', '~> 0.3.15'
39
+ spec.add_development_dependency 'rake', '~> 10.0.4'
40
+ spec.add_development_dependency 'net-ldap', '~> 0.3.1'
41
+ spec.add_development_dependency 'minitest', '~> 5.3.0'
42
+ end
@@ -0,0 +1,190 @@
1
+ require 'ldumbd/table_map'
2
+
3
+ module Ldumbd
4
+ class FilterConverter
5
+ # Public: Converts a parsed LDAP filter to a Sequel dataset filter.
6
+ #
7
+ # base_model: The dataset's base model.
8
+ # filter: A parsed LDAP filter.
9
+ #
10
+ # Examples
11
+ #
12
+ # base_model = User
13
+ # filter = [:and,
14
+ # [:eq, 'uid', nil, 'john'],
15
+ # [:ge, 'uidNumber', nil, '1000']]
16
+ # base_model.where(Ldumbd::FilterConverter.filter_to_sequel(base_model,
17
+ # filter)).sql
18
+ # # => "SELECT * FROM \"users\" WHERE ((\"users\".\"name\" = 'john') AND (\"users\".\"id\" >= '1000'))"
19
+ #
20
+ # Returns a Sequel dataset filter.
21
+ def self.filter_to_sequel(base_model, filter)
22
+ filter = filter.dup
23
+ op = filter.shift
24
+ case op
25
+ when :and, :or, :not
26
+ nested_filter_to_sequel(base_model, filter, op)
27
+ when :true, :false, :eq, :ge, :le, :present, :substrings
28
+ simple_filter_to_sequel(base_model, filter, op)
29
+ else
30
+ raise 'not implemented yet'
31
+ end
32
+ end
33
+
34
+ private
35
+ # Internal: Transforms a nested filter expression into a
36
+ # Sequel::SQL::BooleanExpression
37
+ #
38
+ # base_model: The dataset's base model.
39
+ # filters: An array of parsed LDAP filters.
40
+ # op: The type of nested filter, i.e. :or, :and or :not.
41
+ #
42
+ # Examples
43
+ #
44
+ # base_model = User
45
+ # filters = [[:eq, 'uid', nil, 'john'],
46
+ # [:ge, 'uidNumber', nil, '1000']]
47
+ # op = :and
48
+ # query = Ldumbd::FilterConverter.nested_filter_to_sequel(base_model,
49
+ # filters,
50
+ # op)
51
+ # base_model.where(query).sql
52
+ # # => "SELECT * FROM \"users\" WHERE ((\"users\".\"name\" = 'john') AND (\"users\".\"id\" >= '1000'))"
53
+ def self.nested_filter_to_sequel(base_model, filters, op)
54
+ case op
55
+ when :and
56
+ Sequel.&(*filters.map { |f| filter_to_sequel(base_model, f) })
57
+ when :or
58
+ Sequel.|(*filters.map { |f| filter_to_sequel(base_model, f) })
59
+ when :not
60
+ raise ArgumentError, 'Too many :not arguments' unless filters.size == 1
61
+ subfilter = filters[0]
62
+ # ... id NOT IN (SELECT id FROM ...)
63
+ Sequel.~(id: base_model.where(filter_to_sequel(base_model,
64
+ subfilter)).select(:id))
65
+ end
66
+ end
67
+
68
+ # Internal: Transforms a non-nested filter expression into a sequel
69
+ # dataset filter.
70
+ #
71
+ # base_model: The dataset's base model.
72
+ # filter: A parsed LDAP filter without a filter operator.
73
+ # op: The filter type, e.g. :eq, :le, :ge, ...
74
+ #
75
+ # Examples
76
+ #
77
+ # base_model = User
78
+ # filter = ['uid', nil, 'john']
79
+ # op = :eq
80
+ # query = Ldumbd::FilterConverter.simple_filter_to_sequel(base_model,
81
+ # filter,
82
+ # op)
83
+ # base_model.where(query).sql
84
+ # # => "SELECT * FROM \"users\" WHERE (\"users\".\"name\" = 'john')"
85
+ def self.simple_filter_to_sequel(base_model, filter, op)
86
+ key, value = extract_key_value(base_model, filter, op)
87
+ return { key => value } if key == :users
88
+ return oc_match?(base_model, op, value) if key == :object_class
89
+
90
+ case op
91
+ when :true
92
+ true
93
+ when :false
94
+ false
95
+ when :eq
96
+ # TODO: no case insensitive search support yet
97
+ # User.where(Sequel.function(:lower, :name) => v.downcase)
98
+ { key => value }
99
+ when :le
100
+ Sequel.expr(key) <= Sequel.expr(value)
101
+ when :ge
102
+ Sequel.expr(key) >= Sequel.expr(value)
103
+ when :present
104
+ !key.nil?
105
+ when :substrings
106
+ Sequel.like(key, value)
107
+ end
108
+ end
109
+
110
+ # Internal: Extracts keys and values or Sequel filters for use in
111
+ # simple filters.
112
+ #
113
+ # model: The dataset's model.
114
+ # filter: A parsed LDAP filter without a filter operator.
115
+ # op: The filter type, e.g. :eq, :le, :ge, ...
116
+ #
117
+ # Examples
118
+ #
119
+ # model = User
120
+ # filter = ['uid', nil, 'john']
121
+ # op = :eq
122
+ # Ldumbd::FilterConverter.extract_key_value(model, filter, op)
123
+ # # => [:users__name, "john"]
124
+ def self.extract_key_value(model, filter, op)
125
+ ldap_value = filter[2..-1] || []
126
+ key = extract_key(model, filter)
127
+
128
+ value = if key == :users
129
+ User.where(filter_to_sequel(User, [op, 'uid', nil, ldap_value]))
130
+ elsif op == :substrings
131
+ ldap_value.join('%')
132
+ else
133
+ ldap_value[0]
134
+ end
135
+
136
+ return key, value
137
+ end
138
+
139
+ # Internal: Extracts LDAP keys from parsed LDAP filters and
140
+ # converts them to SQL attributes.
141
+ #
142
+ # model: The dataset model.
143
+ # filter: A parsed LDAP filter without a filter operator.
144
+ #
145
+ # Examples
146
+ #
147
+ # model = User
148
+ # filter = ['uid', nil, 'john']
149
+ # Ldumbd::FilterConverter.extract_key(model, filter)
150
+ # # => :users__name
151
+ def self.extract_key(model, filter)
152
+ ldap_key = filter[0]
153
+ if ldap_key == 'objectClass'
154
+ # object classes are not saved in the database
155
+ :object_class
156
+ else
157
+ TableMap.db_key(model, ldap_key)
158
+ end
159
+ end
160
+
161
+ # Internal: Checks whether a model matches a target object class.
162
+ #
163
+ # model: The model.
164
+ # op: An LDAP filter operator, i.e. :eq, :le, :ge or :substrings.
165
+ # target_oc: The target object class.
166
+ #
167
+ # Examples
168
+ #
169
+ # Ldumbd::FilterConverter.oc_match?(User, :ge, 'posixA')
170
+ # # => true
171
+ #
172
+ # Ldumbd::FilterConverter.oc_match?(Group, :eq, 'nonexistent')
173
+ # # => false
174
+ def self.oc_match?(model, op, target_oc)
175
+ object_classes = TableMap.object_classes(model)
176
+
177
+ case op
178
+ when :eq
179
+ object_classes.any? { |oc| oc == target_oc }
180
+ when :le
181
+ object_classes.any? { |oc| oc <= target_oc }
182
+ when :ge
183
+ object_classes.any? { |oc| oc >= target_oc }
184
+ when :substrings
185
+ target_oc_re = /\A#{Regexp.escape(target_oc).gsub('%', '.*')}\Z/
186
+ object_classes.any? { |oc| oc =~ target_oc_re }
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,3 @@
1
+ class Group < Sequel::Model
2
+ many_to_many :users
3
+ end
@@ -0,0 +1,44 @@
1
+ require 'ldumbd/tree_object'
2
+
3
+ module Ldumbd
4
+ class LdapTree
5
+ def find_by_dn(dn)
6
+ object = @tree[dn]
7
+ unless object
8
+ object = TreeObject.by_dn(@basedn, dn)
9
+ end
10
+ object or raise LDAP::ResultError::NoSuchObject
11
+ end
12
+
13
+ def initialize(basedn)
14
+ @basedn = basedn
15
+ /\Adc=(?<dc>[^,]*)/ =~ basedn
16
+ root = TreeObject.new({ 'dn_prefix' => '',
17
+ 'dc' => dc,
18
+ 'objectClass' => ['top', 'domain']})
19
+ people = TreeObject.new({ 'dn_prefix' => 'ou=People',
20
+ 'ou' => 'People',
21
+ 'objectClass' => ['top',
22
+ 'organizationalUnit'] })
23
+ groups = TreeObject.new({ 'dn_prefix' => 'ou=Groups',
24
+ 'ou' => 'Groups',
25
+ 'objectClass' => ['top',
26
+ 'organizationalUnit'] })
27
+ root.children << people
28
+ root.children << groups
29
+ people.children << User
30
+ groups.children << Group
31
+
32
+ @tree = Hash[
33
+ [root, people, groups].map do |object|
34
+ dn_prefix = object.attributes['dn_prefix']
35
+ [dn(dn_prefix), object]
36
+ end
37
+ ]
38
+ end
39
+
40
+ def dn(dn_prefix)
41
+ [dn_prefix, @basedn].reject(&:empty?).join(',')
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,61 @@
1
+ require 'ldap/server'
2
+
3
+ module Ldumbd
4
+ class Operation < LDAP::Server::Operation
5
+ def initialize(connection, messageID, ldap_tree)
6
+ super(connection, messageID)
7
+ @ldap_tree = ldap_tree
8
+ end
9
+
10
+ def search(basedn, scope, deref, filter)
11
+ search_results(basedn, scope, deref, filter) do |ldap_object|
12
+ dn = @ldap_tree.dn(ldap_object['dn_prefix'])
13
+ object = ldap_object.reject { |k, v| k == 'dn_prefix' }
14
+ send_SearchResultEntry(dn, object)
15
+ end
16
+ end
17
+
18
+ private
19
+ def search_results(basedn, scope, deref, filter, &block)
20
+ case scope
21
+ when LDAP::Server::BaseObject
22
+ search_results_baseobject(basedn, filter, &block)
23
+ when LDAP::Server::SingleLevel
24
+ search_results_singlelevel(basedn, filter, &block)
25
+ when LDAP::Server::WholeSubtree
26
+ search_results_subtree(basedn, filter, &block)
27
+ else
28
+ raise LDAP::ResultError::UnwillingToPerform, 'Invalid search scope'
29
+ end
30
+ end
31
+
32
+ def search_results_baseobject(basedn, filter, &block)
33
+ # the base object only
34
+ object = @ldap_tree.find_by_dn(basedn)
35
+ if object.matches_filter?(filter) && block_given?
36
+ block.call(TableMap.sequel_to_ldap_object(object.attributes))
37
+ end
38
+ end
39
+
40
+ def search_results_singlelevel(basedn, filter, &block)
41
+ # objects immediately subordinate to the base object; does not
42
+ # include the base object itself.
43
+ object = @ldap_tree.find_by_dn(basedn)
44
+ object.each_child(filter) do |r|
45
+ block.call(TableMap.sequel_to_ldap_object(r)) if block_given?
46
+ end
47
+ end
48
+
49
+ def search_results_subtree(basedn, filter, &block)
50
+ # base object and the entire subtree, also includes the base
51
+ # object itself.
52
+ object = @ldap_tree.find_by_dn(basedn)
53
+ if object.matches_filter?(filter) && block_given?
54
+ block.call(TableMap.sequel_to_ldap_object(object.attributes))
55
+ end
56
+ object.each_child(filter, true) do |r|
57
+ block.call(TableMap.sequel_to_ldap_object(r)) if block_given?
58
+ end
59
+ end
60
+ end # Operation
61
+ end # Ldumbd