dm-ldap-adapter 0.4.3-java
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/History.txt +75 -0
- data/MIT-LICENSE +20 -0
- data/README.md +247 -0
- data/ldap-commands.txt +17 -0
- data/lib/adapters/ldap_adapter.rb +370 -0
- data/lib/adapters/noop_transaction.rb +35 -0
- data/lib/dm-ldap-adapter.rb +1 -0
- data/lib/dummy_ldap_resource.rb +60 -0
- data/lib/ldap/array.rb +122 -0
- data/lib/ldap/conditions_2_filter.rb +95 -0
- data/lib/ldap/digest.rb +30 -0
- data/lib/ldap/net_ldap_facade.rb +161 -0
- data/lib/ldap/ruby_ldap_facade.rb +201 -0
- data/lib/ldap/transactions.rb +2 -0
- data/lib/ldap/unboundid_ldap_facade.rb +188 -0
- data/lib/ldap/version.rb +3 -0
- data/lib/ldap_resource.rb +189 -0
- data/spec/assiociations_ldap_adapter_spec.rb +179 -0
- data/spec/authentication_ldap_adapter_spec.rb +32 -0
- data/spec/contact.rb +58 -0
- data/spec/ldap_adapter_spec.rb +239 -0
- data/spec/ldap_array_spec.rb +119 -0
- data/spec/multi_repository_spec.rb +79 -0
- data/spec/multi_value_attributes_spec.rb +161 -0
- data/spec/performance_spec.rb.omit +67 -0
- data/spec/sorting_spec.rb +61 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +163 -0
- metadata +354 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
require "dm-core"
|
2
|
+
|
3
|
+
module Ldap
|
4
|
+
class NoopTransaction
|
5
|
+
|
6
|
+
def close ; end
|
7
|
+
def begin ; end
|
8
|
+
def prepare ; end
|
9
|
+
def commit ; end
|
10
|
+
def rollback ; end
|
11
|
+
def rollback_prepared ; end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module DataMapper
|
17
|
+
module Adapters
|
18
|
+
class LdapAdapter
|
19
|
+
def transaction_primitive
|
20
|
+
::Ldap::NoopTransaction.new
|
21
|
+
end
|
22
|
+
def push_transaction(transaction)
|
23
|
+
@transaction = transaction
|
24
|
+
end
|
25
|
+
|
26
|
+
def pop_transaction
|
27
|
+
@transaction
|
28
|
+
end
|
29
|
+
|
30
|
+
def current_transaction
|
31
|
+
@transaction
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'adapter/ldap-adapter'
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'slf4r/logger'
|
2
|
+
require 'ldap/digest'
|
3
|
+
|
4
|
+
# dummy implementation which turns the extra ldap configuration noops
|
5
|
+
module DataMapper
|
6
|
+
module Resource
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
include ::Slf4r::Logger
|
11
|
+
|
12
|
+
def ldap_properties(resource = nil, &block)
|
13
|
+
if block
|
14
|
+
@ldap_properties = block
|
15
|
+
elsif resource.instance_of? Hash
|
16
|
+
@ldap_properties = resource
|
17
|
+
logger.debug { "ldap_properties=#{@ldap_properties.inspect}" }
|
18
|
+
elsif resource
|
19
|
+
logger.debug { "ldap_properties=#{@ldap_properties.call(resource).inspect}" }
|
20
|
+
else
|
21
|
+
logger.debug { "ldap_properties=#{@ldap_properties.inspect}" }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def treebase(resource = nil, &block)
|
26
|
+
if block
|
27
|
+
@treebase = block
|
28
|
+
elsif resource.instance_of? String
|
29
|
+
@treebase = resource
|
30
|
+
logger.debug { "treebase=#{@treebase.inspect}" }
|
31
|
+
elsif resource
|
32
|
+
logger.debug { "treebase=#{@treebase.call(resource).inspect}" }
|
33
|
+
else
|
34
|
+
logger.debug { "treebase=#{@treebase}" }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def dn_prefix(resource = nil, &block)
|
39
|
+
if block
|
40
|
+
@dn_prefix = block
|
41
|
+
elsif resource.instance_of? Hash
|
42
|
+
@dn_prefix = resource
|
43
|
+
logger.debug { "dn_prefix=#{@dn_prefix.inspect}" }
|
44
|
+
elsif resource
|
45
|
+
logger.debug { "dn_prefix=#{@dn_prefix.call(resource).inspect}" }
|
46
|
+
else
|
47
|
+
logger.debug { "dn_prefix=#{dn_prefix}" }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def multivalue_field(field = nil)
|
52
|
+
logger.debug { "multivalue_field = #{field}" } if field
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def authenticate(password)
|
57
|
+
raise "NotImplemented"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/ldap/array.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'dm-core'
|
2
|
+
module Ldap
|
3
|
+
class Array < ::Array
|
4
|
+
|
5
|
+
def initialize(resource, property, *args)
|
6
|
+
setup(resource, property)
|
7
|
+
super(args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def setup(resource, property)
|
11
|
+
@resource = resource
|
12
|
+
@property = property
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
alias :push! :push
|
17
|
+
|
18
|
+
def []=(k, v)
|
19
|
+
ar = [self].flatten
|
20
|
+
ar[k] = v
|
21
|
+
@resource.send("#{@property.name}=".to_sym, ar)
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
def <<(element)
|
26
|
+
push(element)
|
27
|
+
end
|
28
|
+
|
29
|
+
def push(element)
|
30
|
+
ar = [self].flatten
|
31
|
+
ar.push(element)
|
32
|
+
@resource.send("#{@property.name}=".to_sym, ar)
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
alias :delete! :delete
|
37
|
+
|
38
|
+
def delete(element)
|
39
|
+
ar = [self].flatten
|
40
|
+
ar.delete(element)
|
41
|
+
@resource.send(:"#{@property.name}=", ar)
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class LdapArray < ::DataMapper::Property::Text
|
47
|
+
|
48
|
+
default Proc.new { |r,p| Ldap::Array.new(r,p) }
|
49
|
+
|
50
|
+
def custom?
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
def primitive?(value)
|
55
|
+
super || value.kind_of?(::Array)
|
56
|
+
end
|
57
|
+
|
58
|
+
def load(value)
|
59
|
+
result = case value
|
60
|
+
when ::String then value[1, value.size-2].split('","').to_a.freeze
|
61
|
+
when ::Array then value.freeze
|
62
|
+
else
|
63
|
+
[]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def dump(value)
|
68
|
+
result = case value
|
69
|
+
when LdapArray then '"' + value.join('","') + '"'
|
70
|
+
when ::Array then '"' + value.join('","') + '"'
|
71
|
+
when ::String then '"' + value.to_s + '"'
|
72
|
+
else
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# keep the *args so it works for both DM-1.1.x and DM-1.0.x
|
78
|
+
def initialize(_model = nil, _name = nil, options = {}, *args)
|
79
|
+
super
|
80
|
+
|
81
|
+
add_writer(model,name) unless options[:writer] == :private || options[:accessor] == :private
|
82
|
+
add_reader(model,name) unless options[:reader] == :private || options[:accessor] == :private
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def add_reader(model, name)
|
88
|
+
#Creates instance method for reader
|
89
|
+
model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
90
|
+
def #{name}
|
91
|
+
attr_data = attribute_get(:#{name})
|
92
|
+
|
93
|
+
case attr_data
|
94
|
+
when Ldap::Array
|
95
|
+
attr_data.setup(self, properties[:#{name}])
|
96
|
+
else
|
97
|
+
new_ldap_array = Ldap::Array.new(self, properties[:#{name}])
|
98
|
+
new_ldap_array.replace(attr_data || [])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
RUBY
|
102
|
+
end
|
103
|
+
|
104
|
+
def add_writer(model, name)
|
105
|
+
#Creates instance method for writer
|
106
|
+
model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
107
|
+
def #{name}=(input)
|
108
|
+
data = case input
|
109
|
+
when Ldap::Array
|
110
|
+
input.setup(self, properties[:#{name}])
|
111
|
+
else
|
112
|
+
new_ldap_array = Ldap::Array.new(self, properties[:#{name}])
|
113
|
+
new_ldap_array.replace(input || [])
|
114
|
+
end
|
115
|
+
|
116
|
+
attribute_set(:#{name}, data)
|
117
|
+
end
|
118
|
+
RUBY
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'net/ldap'
|
2
|
+
|
3
|
+
module Ldap
|
4
|
+
class Conditions2Filter
|
5
|
+
|
6
|
+
@@logger = ::Slf4r::LoggerFacade.new(::Ldap::Conditions2Filter)
|
7
|
+
|
8
|
+
# @param Array of conditions for the search
|
9
|
+
# @return Array of Hashes with a name/values pair for each attribute
|
10
|
+
def self.convert(conditions)
|
11
|
+
@@logger.debug { "conditions #{conditions.inspect}" }
|
12
|
+
filters = []
|
13
|
+
conditions.each do |cond|
|
14
|
+
c = cond[2]
|
15
|
+
case cond[0]
|
16
|
+
when :or_operator
|
17
|
+
f = nil
|
18
|
+
cond[1].each do |cc|
|
19
|
+
ff = case cc[0]
|
20
|
+
when :eql
|
21
|
+
Net::LDAP::Filter.eq( cc[1].to_s, cc[2].to_s )
|
22
|
+
when :gte
|
23
|
+
Net::LDAP::Filter.ge( cc[1].to_s, cc[2].to_s )
|
24
|
+
when :lte
|
25
|
+
Net::LDAP::Filter.le( cc[1].to_s, cc[2].to_s )
|
26
|
+
when :like
|
27
|
+
Net::LDAP::Filter.eq( cc[1].to_s, cc[2].to_s.gsub(/%/, "*").gsub(/_/, "*").gsub(/\*\*/, "*") )
|
28
|
+
else
|
29
|
+
logger.error(cc[0].to_s + " needs coding")
|
30
|
+
end
|
31
|
+
if f
|
32
|
+
f = f | ff
|
33
|
+
else
|
34
|
+
f = ff
|
35
|
+
end
|
36
|
+
end
|
37
|
+
when :eql
|
38
|
+
if c.nil?
|
39
|
+
f = ~ Net::LDAP::Filter.pres( cond[1].to_s )
|
40
|
+
elsif c.respond_to? :each
|
41
|
+
f = nil
|
42
|
+
c.each do |cc|
|
43
|
+
if f
|
44
|
+
f = f | Net::LDAP::Filter.eq( cond[1].to_s, cc.to_s )
|
45
|
+
else
|
46
|
+
f = Net::LDAP::Filter.eq( cond[1].to_s, cc.to_s )
|
47
|
+
end
|
48
|
+
end
|
49
|
+
#elsif c.class == Range
|
50
|
+
# p c
|
51
|
+
# f = Net::LDAP::Filter.ge( cond[1].to_s, c.begin.to_s ) & Net::LDAP::Filter.le( cond[1].to_s, c.end.to_s )
|
52
|
+
else
|
53
|
+
f = Net::LDAP::Filter.eq( cond[1].to_s, c.to_s )
|
54
|
+
end
|
55
|
+
when :gte
|
56
|
+
f = Net::LDAP::Filter.ge( cond[1].to_s, c.to_s )
|
57
|
+
when :lte
|
58
|
+
f = Net::LDAP::Filter.le( cond[1].to_s, c.to_s )
|
59
|
+
when :not
|
60
|
+
if c.nil?
|
61
|
+
f = Net::LDAP::Filter.pres( cond[1].to_s )
|
62
|
+
elsif c.respond_to? :each
|
63
|
+
f = nil
|
64
|
+
c.each do |cc|
|
65
|
+
if f
|
66
|
+
f = f | Net::LDAP::Filter.eq( cond[1].to_s, cc.to_s )
|
67
|
+
else
|
68
|
+
f = Net::LDAP::Filter.eq( cond[1].to_s, cc.to_s )
|
69
|
+
end
|
70
|
+
end
|
71
|
+
f = ~ f
|
72
|
+
else
|
73
|
+
f = ~ Net::LDAP::Filter.eq( cond[1].to_s, c.to_s )
|
74
|
+
end
|
75
|
+
when :like
|
76
|
+
f = Net::LDAP::Filter.eq( cond[1].to_s, c.to_s.gsub(/%/, "*").gsub(/_/, "*").gsub(/\*\*/, "*") )
|
77
|
+
else
|
78
|
+
logger.error(cond[0].to_s + " needs coding")
|
79
|
+
end
|
80
|
+
filters << f if f
|
81
|
+
end
|
82
|
+
|
83
|
+
filter = nil
|
84
|
+
filters.each do |f|
|
85
|
+
if filter.nil?
|
86
|
+
filter = f
|
87
|
+
else
|
88
|
+
filter = filter & f
|
89
|
+
end
|
90
|
+
end
|
91
|
+
@@logger.debug { "search filter: (#{filter.to_s})" }
|
92
|
+
filter
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/ldap/digest.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
begin
|
2
|
+
require 'sha1'
|
3
|
+
rescue LoadError
|
4
|
+
# ruby1.9.x
|
5
|
+
require 'digest/sha1'
|
6
|
+
SHA1 = Digest::SHA1
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'base64'
|
10
|
+
module Ldap
|
11
|
+
class Digest
|
12
|
+
# method from openldap faq which produces the userPassword attribute
|
13
|
+
# for the ldap
|
14
|
+
# @param secret String the password
|
15
|
+
# @param salt String the salt for the password digester
|
16
|
+
# @return the encoded password/salt
|
17
|
+
def self.ssha(secret, salt)
|
18
|
+
(salt.empty? ? "{SHA}": "{SSHA}") +
|
19
|
+
Base64.encode64(::Digest::SHA1.digest(secret + salt) + salt).gsub(/\n/, '')
|
20
|
+
end
|
21
|
+
|
22
|
+
# method from openldap faq which produces the userPassword attribute
|
23
|
+
# for the ldap
|
24
|
+
# @param secret String the password
|
25
|
+
# @return the encoded password
|
26
|
+
def self.sha(secret)
|
27
|
+
ssha(secret, "")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'net/ldap'
|
2
|
+
require 'ldap/conditions_2_filter'
|
3
|
+
|
4
|
+
module Ldap
|
5
|
+
class NetLdapFacade
|
6
|
+
|
7
|
+
# @param config Hash for the ldap connection
|
8
|
+
def self.open(config)
|
9
|
+
Net::LDAP.open( config ) do |ldap|
|
10
|
+
yield ldap
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
include ::Slf4r::Logger
|
15
|
+
|
16
|
+
# @param config Hash for the ldap connection
|
17
|
+
def initialize(config)
|
18
|
+
if config.is_a? Hash
|
19
|
+
@ldap = Net::LDAP.new( config )
|
20
|
+
else
|
21
|
+
@ldap = config
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def retrieve_next_id(treebase, key_field)
|
26
|
+
base = "#{treebase},#{@ldap.base}"
|
27
|
+
id_sym = key_field.downcase.to_sym
|
28
|
+
max = 0
|
29
|
+
@ldap.search( :base => base,
|
30
|
+
:attributes => [key_field],
|
31
|
+
:return_result => false ) do |entry|
|
32
|
+
n = entry[id_sym].first.to_i
|
33
|
+
max = n if max < n
|
34
|
+
end
|
35
|
+
max + 1
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param dn_prefix String the prefix of the dn
|
39
|
+
# @param treebase the treebase of the dn or any search
|
40
|
+
# @param key_field field which carries the integer unique id of the entity
|
41
|
+
# @param props Hash of the ldap attributes of the new ldap object
|
42
|
+
# @return nil in case of an error or the new id of the created object
|
43
|
+
def create_object(dn_prefix, treebase, key_field, props, silence = false)
|
44
|
+
base = "#{treebase},#{@ldap.base}"
|
45
|
+
if @ldap.add( :dn => dn(dn_prefix, treebase),
|
46
|
+
:attributes => props) || @ldap.get_operation_result.code.to_s == "0"
|
47
|
+
props[key_field.to_sym]
|
48
|
+
else
|
49
|
+
unless silence
|
50
|
+
msg = ldap_error("create",
|
51
|
+
dn(dn_prefix, treebase)) + "\n\t#{props.inspect}"
|
52
|
+
# TODO maybe raise always an error
|
53
|
+
if @ldap.get_operation_result.code.to_s == "68"
|
54
|
+
raise ::DataMapper::PersistenceError.new(msg)
|
55
|
+
else
|
56
|
+
logger.warn(msg)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @param treebase the treebase of the search
|
64
|
+
# @param key_fields Array of fields which carries the integer unique id(s) of the entity
|
65
|
+
# @param Array of conditions for the search
|
66
|
+
# @return Array of Hashes with a name/values pair for each attribute
|
67
|
+
def read_objects(treebase, key_fields, conditions, field_names, order_field = nil)
|
68
|
+
result = []
|
69
|
+
filter = Conditions2Filter.convert(conditions)
|
70
|
+
@ldap.search( :base => "#{treebase},#{@ldap.base}",
|
71
|
+
:attributes => field_names,
|
72
|
+
:filter => filter ) do |res|
|
73
|
+
mapp = to_map(field_names, res)
|
74
|
+
|
75
|
+
#puts map[key_field.to_sym]
|
76
|
+
# TODO maybe make filter which removes this unless
|
77
|
+
# TODO move this into the ldap_Adapter to make it more general, so that
|
78
|
+
# all field with Integer gets converted, etc
|
79
|
+
result << mapp if key_fields.detect do |key_field|
|
80
|
+
mapp.keys.detect {|k| k.to_s.downcase == key_field.downcase }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
result
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
# @param dn_prefix String the prefix of the dn
|
88
|
+
# @param treebase the treebase of the dn or any search
|
89
|
+
# @param actions the add/replace/delete actions on the attributes
|
90
|
+
# @return nil in case of an error or true
|
91
|
+
def update_object(dn_prefix, treebase, actions)
|
92
|
+
if @ldap.modify( :dn => dn(dn_prefix, treebase),
|
93
|
+
:operations => actions ) || @ldap.get_operation_result.code.to_s == "0"
|
94
|
+
true
|
95
|
+
else
|
96
|
+
logger.warn(ldap_error("update",
|
97
|
+
dn(dn_prefix, treebase) + "\n\t#{actions.inspect}"))
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# @param dn_prefix String the prefix of the dn
|
103
|
+
# @param treebase the treebase of the dn or any search
|
104
|
+
# @return nil in case of an error or true
|
105
|
+
def delete_object(dn_prefix, treebase)
|
106
|
+
if @ldap.delete( :dn => dn(dn_prefix, treebase) )
|
107
|
+
true
|
108
|
+
else
|
109
|
+
logger.warn(ldap_error("delete",
|
110
|
+
dn(dn_prefix, treebase)))
|
111
|
+
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
# @param dn String for identifying the ldap object
|
118
|
+
# @param password String to be used for authenticate to the dn
|
119
|
+
def authenticate(dn, password)
|
120
|
+
Net::LDAP.new( { :host => @ldap.host,
|
121
|
+
:port => @ldap.port,
|
122
|
+
:auth => {
|
123
|
+
:method => :simple,
|
124
|
+
:username => dn,
|
125
|
+
:password => password
|
126
|
+
},
|
127
|
+
:base => @ldap.base
|
128
|
+
} ).bind
|
129
|
+
end
|
130
|
+
|
131
|
+
# helper to concat the dn from the various parts
|
132
|
+
# @param dn_prefix String the prefix of the dn
|
133
|
+
# @param treebase the treebase of the dn or any search
|
134
|
+
# @return the complete dn String
|
135
|
+
def dn(dn_prefix, treebase)
|
136
|
+
"#{dn_prefix},#{treebase},#{@ldap.base}"
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
# helper to extract the Hash from the ldap search result
|
142
|
+
# @param Entry from the ldap_search
|
143
|
+
# @return Hash with name/value pairs of the entry
|
144
|
+
def to_map(field_names, entry)
|
145
|
+
fields = {:dn => :dn}
|
146
|
+
field_names.each { |f| fields[f.downcase.to_sym] = f.to_sym }
|
147
|
+
def entry.map
|
148
|
+
@myhash
|
149
|
+
end
|
150
|
+
result = {}
|
151
|
+
entry.map.each do |k,v|
|
152
|
+
result[fields[k]] = v
|
153
|
+
end
|
154
|
+
result
|
155
|
+
end
|
156
|
+
|
157
|
+
def ldap_error(method, dn)
|
158
|
+
"#{method} error: (#{@ldap.get_operation_result.code}) #{@ldap.get_operation_result.message}\n\tDN: #{dn}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|