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
data/Gemfile
ADDED
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
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
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,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,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
|
data/lib/ldumbd/group.rb
ADDED
@@ -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
|