dm-ldap-adapter 0.4.3-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|