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