dm-ldap-adapter 0.2.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/History.txt +10 -0
- data/MIT-LICENSE +20 -0
- data/Manifest.txt +24 -0
- data/README-example.markdown +18 -0
- data/README.txt +188 -0
- data/Rakefile +35 -0
- data/example/identity_map.rb +76 -0
- data/example/posix.rb +166 -0
- data/ldap-commands.txt +14 -0
- data/lib/adapters/ldap_adapter.rb +244 -0
- data/lib/adapters/memory_adapter.rb +79 -0
- data/lib/adapters/simple_adapter.rb +198 -0
- data/lib/dummy_ldap_resource.rb +60 -0
- data/lib/ldap/digest.rb +23 -0
- data/lib/ldap/ldap_facade.rb +210 -0
- data/lib/ldap/ldap_facade_mock.rb +56 -0
- data/lib/ldap/version.rb +3 -0
- data/lib/ldap_resource.rb +107 -0
- data/spec/assiociations_ldap_adapter_spec.rb +164 -0
- data/spec/authentication_ldap_adapter_spec.rb +37 -0
- data/spec/ldap_adapter_spec.rb +213 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +147 -0
- data/test.ldif +46 -0
- metadata +110 -0
@@ -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)
|
26
|
+
if block
|
27
|
+
@treebase = block
|
28
|
+
elsif resource.instance_of? Hash
|
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/digest.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Ldap
|
2
|
+
class Digest
|
3
|
+
# method from openldap faq which produces the userPassword attribute
|
4
|
+
# for the ldap
|
5
|
+
# @param secret String the password
|
6
|
+
# @param salt String the salt for the password digester
|
7
|
+
# @return the encoded password/salt
|
8
|
+
def self.ssha(secret, salt)
|
9
|
+
require 'sha1'
|
10
|
+
require 'base64'
|
11
|
+
(salt.empty? ? "{SHA}": "{SSHA}") +
|
12
|
+
Base64.encode64(::Digest::SHA1.digest(secret + salt) + salt).gsub(/\n/, '')
|
13
|
+
end
|
14
|
+
|
15
|
+
# method from openldap faq which produces the userPassword attribute
|
16
|
+
# for the ldap
|
17
|
+
# @param secret String the password
|
18
|
+
# @return the encoded password
|
19
|
+
def self.sha(secret)
|
20
|
+
ssha(secret, "")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
require "net/ldap.rb"
|
2
|
+
module Ldap
|
3
|
+
class LdapFacade
|
4
|
+
|
5
|
+
# @param config Hash for the ldap connection
|
6
|
+
def self.open(config)
|
7
|
+
Net::LDAP.open( config ) do |ldap|
|
8
|
+
yield ldap
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
include ::Slf4r::Logger
|
13
|
+
|
14
|
+
# @param config Hash for the ldap connection
|
15
|
+
def initialize(config)
|
16
|
+
if config.is_a? Hash
|
17
|
+
@ldap = Net::LDAP.new( config )
|
18
|
+
else
|
19
|
+
@ldap = config
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param dn_prefix String the prefix of the dn
|
24
|
+
# @param treebase the treebase of the dn or any search
|
25
|
+
# @param key_field field which carries the integer unique id of the entity
|
26
|
+
# @param props Hash of the ldap attributes of the new ldap object
|
27
|
+
# @return nil in case of an error or the new id of the created object
|
28
|
+
def create_object(dn_prefix, treebase, key_field, props, silence = false)
|
29
|
+
base = "#{treebase},#{@ldap.base}"
|
30
|
+
id_sym = key_field.downcase.to_sym
|
31
|
+
max = 0
|
32
|
+
@ldap.search( :base => base,
|
33
|
+
:attributes => [key_field],
|
34
|
+
:return_result => false ) do |entry|
|
35
|
+
n = entry[id_sym].first.to_i
|
36
|
+
max = n if max < n
|
37
|
+
end
|
38
|
+
id = max + 1
|
39
|
+
props[id_sym] = "#{id}"
|
40
|
+
if @ldap.add( :dn => dn(dn_prefix, treebase),
|
41
|
+
:attributes => props)
|
42
|
+
id
|
43
|
+
else
|
44
|
+
unless silence
|
45
|
+
msg = ldap_error("create",
|
46
|
+
dn(dn_prefix, treebase)) + "\n\t#{props.inspect}"
|
47
|
+
# TODO maybe raise always an error
|
48
|
+
if @ldap.get_operation_result.code == 68
|
49
|
+
raise ::DataMapper::PersistenceError.new(msg)
|
50
|
+
else
|
51
|
+
logger.warn(msg)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param treebase the treebase of the search
|
59
|
+
# @param key_fields Array of fields which carries the integer unique id(s) of the entity
|
60
|
+
# @param Array of conditions for the search
|
61
|
+
# @return Array of Hashes with a name/values pair for each attribute
|
62
|
+
def read_objects(treebase, key_fields, conditions)
|
63
|
+
filters = []
|
64
|
+
conditions.each do |cond|
|
65
|
+
c = cond[2]
|
66
|
+
case cond[0]
|
67
|
+
when :eql
|
68
|
+
if c.nil?
|
69
|
+
f = ~ Net::LDAP::Filter.pres( cond[1].to_s )
|
70
|
+
elsif c.class == Array
|
71
|
+
f = nil
|
72
|
+
c.each do |cc|
|
73
|
+
if f
|
74
|
+
f = f | Net::LDAP::Filter.eq( cond[1].to_s, cc.to_s )
|
75
|
+
else
|
76
|
+
f = Net::LDAP::Filter.eq( cond[1].to_s, cc.to_s )
|
77
|
+
end
|
78
|
+
end
|
79
|
+
#elsif c.class == Range
|
80
|
+
# p c
|
81
|
+
# 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 )
|
82
|
+
else
|
83
|
+
f = Net::LDAP::Filter.eq( cond[1].to_s, c.to_s )
|
84
|
+
end
|
85
|
+
when :gte
|
86
|
+
f = Net::LDAP::Filter.ge( cond[1].to_s, c.to_s )
|
87
|
+
when :lte
|
88
|
+
f = Net::LDAP::Filter.le( cond[1].to_s, c.to_s )
|
89
|
+
when :not
|
90
|
+
if c.nil?
|
91
|
+
f = Net::LDAP::Filter.pres( cond[1].to_s )
|
92
|
+
elsif c.class == Array
|
93
|
+
f = nil
|
94
|
+
c.each do |cc|
|
95
|
+
if f
|
96
|
+
f = f | Net::LDAP::Filter.eq( cond[1].to_s, cc.to_s )
|
97
|
+
else
|
98
|
+
f = Net::LDAP::Filter.eq( cond[1].to_s, cc.to_s )
|
99
|
+
end
|
100
|
+
end
|
101
|
+
f = ~ f
|
102
|
+
else
|
103
|
+
f = ~ Net::LDAP::Filter.eq( cond[1].to_s, c.to_s )
|
104
|
+
end
|
105
|
+
when :like
|
106
|
+
f = Net::LDAP::Filter.eq( cond[1].to_s, c.to_s.gsub(/%/, "*").gsub(/_/, "*").gsub(/\*\*/, "*") )
|
107
|
+
else
|
108
|
+
logger.error(cond[0].to_s + " needs coding")
|
109
|
+
end
|
110
|
+
filters << f
|
111
|
+
end
|
112
|
+
|
113
|
+
filter = nil
|
114
|
+
filters.each do |f|
|
115
|
+
if filter.nil?
|
116
|
+
filter = f
|
117
|
+
else
|
118
|
+
filter = filter & f
|
119
|
+
end
|
120
|
+
end
|
121
|
+
logger.debug { "search filter: (#{filter.to_s})" }
|
122
|
+
result = []
|
123
|
+
@ldap.search( :base => "#{treebase},#{@ldap.base}",
|
124
|
+
:filter => filter ) do |res|
|
125
|
+
map = to_map(res)
|
126
|
+
#puts map[key_field.to_sym]
|
127
|
+
# TODO maybe make filter which removes this unless
|
128
|
+
# TODO move this into the ldap_Adapter to make it more general, so that
|
129
|
+
# all field with Integer gets converted, etc
|
130
|
+
result << map if key_fields.select do |key_field|
|
131
|
+
if map.member? key_field.to_sym
|
132
|
+
# convert field to integer
|
133
|
+
map[key_field.to_sym] = [map[key_field.to_sym].collect { |k| k.to_i != 0 ? k.to_s : k }].flatten
|
134
|
+
true
|
135
|
+
end
|
136
|
+
end.size > 0 # i.e. there was at least one key_field in the map
|
137
|
+
end
|
138
|
+
result
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
# @param dn_prefix String the prefix of the dn
|
143
|
+
# @param treebase the treebase of the dn or any search
|
144
|
+
# @param actions the add/replace/delete actions on the attributes
|
145
|
+
# @return nil in case of an error or true
|
146
|
+
def update_object(dn_prefix, treebase, actions)
|
147
|
+
if @ldap.modify( :dn => dn(dn_prefix, treebase),
|
148
|
+
:operations => actions )
|
149
|
+
true
|
150
|
+
else
|
151
|
+
logger.warn(ldap_error("update",
|
152
|
+
dn(dn_prefix, treebase) + "\n\t#{actions.inspect}"))
|
153
|
+
nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# @param dn_prefix String the prefix of the dn
|
158
|
+
# @param treebase the treebase of the dn or any search
|
159
|
+
# @return nil in case of an error or true
|
160
|
+
def delete_object(dn_prefix, treebase)
|
161
|
+
if @ldap.delete( :dn => dn(dn_prefix, treebase) )
|
162
|
+
true
|
163
|
+
else
|
164
|
+
logger.warn(ldap_error("delete",
|
165
|
+
dn(dn_prefix, treebase)))
|
166
|
+
|
167
|
+
nil
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
# @param dn String for identifying the ldap object
|
173
|
+
# @param password String to be used for authenticate to the dn
|
174
|
+
def authenticate(dn, password)
|
175
|
+
Net::LDAP.new( { :host => @ldap.host,
|
176
|
+
:port => @ldap.port,
|
177
|
+
:auth => {
|
178
|
+
:method => :simple,
|
179
|
+
:username => dn,
|
180
|
+
:password => password
|
181
|
+
},
|
182
|
+
:base => @ldap.base
|
183
|
+
} ).bind
|
184
|
+
end
|
185
|
+
|
186
|
+
# helper to concat the dn from the various parts
|
187
|
+
# @param dn_prefix String the prefix of the dn
|
188
|
+
# @param treebase the treebase of the dn or any search
|
189
|
+
# @return the complete dn String
|
190
|
+
def dn(dn_prefix, treebase)
|
191
|
+
"#{dn_prefix},#{treebase},#{@ldap.base}"
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
# helper to extract the Hash from the ldap search result
|
197
|
+
# @param Entry from the ldap_search
|
198
|
+
# @return Hash with name/value pairs of the entry
|
199
|
+
def to_map(entry)
|
200
|
+
def entry.map
|
201
|
+
@myhash
|
202
|
+
end
|
203
|
+
entry.map
|
204
|
+
end
|
205
|
+
|
206
|
+
def ldap_error(method, dn)
|
207
|
+
"#{method} error: (#{@ldap.get_operation_result.code}) #{@ldap.get_operation_result.message}\n\tDN: #{dn}"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Ldap
|
2
|
+
class LdapFacade
|
3
|
+
|
4
|
+
def self.open(config)
|
5
|
+
puts "open"
|
6
|
+
p config
|
7
|
+
puts
|
8
|
+
yield "dummy"
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(uri)
|
12
|
+
puts "new #{self.hash}"
|
13
|
+
p uri
|
14
|
+
puts
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_object(treebase, dn_prefix, key_field, props, silence = false)
|
18
|
+
options = { :dn_prefix => dn_prefix,
|
19
|
+
:treebase => treebase,
|
20
|
+
:key_field => key_field,
|
21
|
+
:properties => props }
|
22
|
+
puts "create #{self.hash}"
|
23
|
+
p options
|
24
|
+
puts
|
25
|
+
@@count ||= 0
|
26
|
+
@@count += 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def read_objects(treebase, key_field, conditions, many = false)
|
30
|
+
options = { :treebase => treebase,
|
31
|
+
:key_field => key_field,
|
32
|
+
:conditions => conditions, :many => many }
|
33
|
+
puts "read #{self.hash}"
|
34
|
+
p options
|
35
|
+
puts
|
36
|
+
[] if many
|
37
|
+
end
|
38
|
+
|
39
|
+
def update_object(treebase, dn_prefix, actions)
|
40
|
+
options = { :dn_prefix => dn_prefix,
|
41
|
+
:treebase => treebase,
|
42
|
+
:actions => actions }
|
43
|
+
puts "update #{self.hash}"
|
44
|
+
p options
|
45
|
+
puts
|
46
|
+
end
|
47
|
+
|
48
|
+
def delete_object(treebase, dn_prefix)
|
49
|
+
options = { :dn_prefix => dn_prefix,
|
50
|
+
:treebase => treebase }
|
51
|
+
puts "delete #{self.hash}"
|
52
|
+
p options
|
53
|
+
puts
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/ldap/version.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'ldap/digest'
|
2
|
+
|
3
|
+
module DataMapper
|
4
|
+
module Resource
|
5
|
+
module ClassMethods
|
6
|
+
|
7
|
+
# if called without parameter or block the given properties get returned.
|
8
|
+
# if called with a block then the block gets stored. if called with new
|
9
|
+
# properties they get stored. if called with a Resource then either the
|
10
|
+
# stored block gets called with that Resource or the stored properties get
|
11
|
+
# returned.
|
12
|
+
# @param [Hash,DataMapper::Resource] properties_or_resource either a Hash with properties, a Resource or nil
|
13
|
+
# @param [block] &block to be stored for later calls when properties_or_resource is nil
|
14
|
+
# @return [Hash] when called with a Resource
|
15
|
+
def ldap_properties(properties_or_resource = nil, &block)
|
16
|
+
if properties_or_resource
|
17
|
+
if properties_or_resource.instance_of? Hash
|
18
|
+
@ldap_properties = properties_or_resource
|
19
|
+
elsif @ldap_properties.instance_of? Hash
|
20
|
+
@ldap_properties
|
21
|
+
else
|
22
|
+
@ldap_properties.call(properties_or_resource)
|
23
|
+
end
|
24
|
+
else
|
25
|
+
@ldap_properties = block
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# if called without parameter or block the given treebase gets returned.
|
30
|
+
# if called with a block then the block gets stored. if called with a
|
31
|
+
# String then it gets stored. if called with a Resource then either the
|
32
|
+
# stored block gets called with that Resource or the stored String gets
|
33
|
+
# returned.
|
34
|
+
# @param [String,DataMapper::Resource] treebase_or_resource either a String, a Resource or nil
|
35
|
+
# @param [block] &block to be stored for later calls when base_or_resource is nil
|
36
|
+
# @return [String] when called with a Resource
|
37
|
+
def treebase(base_or_resource = nil, &block)
|
38
|
+
if base_or_resource
|
39
|
+
if base_or_resource.instance_of? String
|
40
|
+
@treebase = base_or_resource
|
41
|
+
elsif @treebase.instance_of? String
|
42
|
+
@treebase
|
43
|
+
else
|
44
|
+
@treebase.call(base_or_resource)
|
45
|
+
end
|
46
|
+
else
|
47
|
+
if block
|
48
|
+
@treebase = block
|
49
|
+
else # backwards compatibility
|
50
|
+
@treebase
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# if called without parameter or block the given dn_prefix gets returned.
|
56
|
+
# if called with a block then the block gets stored. if called with a
|
57
|
+
# String then it gets stored. if called with a Resource then either the
|
58
|
+
# stored block gets called with that Resource or the stored String gets
|
59
|
+
# returned.
|
60
|
+
# @param [String,DataMapper::Resource] prefix_or_resource either a String, a Resource or nil
|
61
|
+
# @param [&block] block to be stored for later calls
|
62
|
+
# @return [String, nil] when called with a Resource
|
63
|
+
def dn_prefix(prefix_or_resource = nil, &block)
|
64
|
+
if prefix_or_resource
|
65
|
+
if prefix_or_resource.instance_of? String
|
66
|
+
@ldap_dn = prefix_or_resource
|
67
|
+
elsif @ldap_dn.instance_of? String
|
68
|
+
@ldap_dn
|
69
|
+
else
|
70
|
+
@ldap_dn.call(prefix_or_resource)
|
71
|
+
end
|
72
|
+
else
|
73
|
+
@ldap_dn = block
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# if called without parameter then the stored field gets returned
|
78
|
+
# otherwise the given parameters gets stored
|
79
|
+
# @param [Symbol, String] field a new multivalue_field
|
80
|
+
# @return [Symbol] the multivalue_field
|
81
|
+
def multivalue_field(field = nil)
|
82
|
+
if field.nil?
|
83
|
+
@ldap_multivalue_field
|
84
|
+
else
|
85
|
+
@ldap_multivalue_field = field.to_sym
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# authenticate the current resource against the stored password
|
91
|
+
# @param [String] password to authenticate
|
92
|
+
# @return [TrueClass, FalseClass] whether password was right or wrong
|
93
|
+
def authenticate(password)
|
94
|
+
ldap.authenticate(ldap.dn(self.class.dn_prefix(self),
|
95
|
+
self.class.treebase),
|
96
|
+
password)
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
# short cut to the ldap facade
|
101
|
+
# @return [Ldap::LdapFacade]
|
102
|
+
def ldap
|
103
|
+
raise "not an ldap adapter #{repository.adapter.name}" unless repository.adapter.respond_to? :ldap
|
104
|
+
repository.adapter.ldap
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|