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
data/ldap-commands.txt
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# populate ldap with basic data
|
2
|
+
ldapadd -x -w behappy -c -D "cn=admin,dc=example,dc=com" < test.ldif
|
3
|
+
|
4
|
+
# search the whole domain
|
5
|
+
ldapsearch -x -w behappy -c -D "cn=admin,dc=example,dc=com" -b 'dc=example,dc=com'
|
6
|
+
|
7
|
+
# search people
|
8
|
+
ldapsearch -x -w behappy -c -D "cn=admin,dc=example,dc=com" -b 'ou=people,dc=example,dc=com'
|
9
|
+
|
10
|
+
#search groups
|
11
|
+
ldapsearch -x -w behappy -c -D "cn=admin,dc=example,dc=com" -b 'ou=groups,dc=example,dc=com'
|
12
|
+
|
13
|
+
# printout delete commands for all people
|
14
|
+
ldapsearch -x -w behappy -c -D "cn=admin,dc=example,dc=com" -b 'ou=people,dc=example,dc=com' "uid=*" | grep ^uid: | sed -e "s/^.....//" -e 's/$/,ou=people,dc=example,dc=com"/' -e 's/^/-x -w behappy -c -D "cn=admin,dc=example,dc=com" "uid=/' | xargs -L 1 echo ldapdelete
|
@@ -0,0 +1,244 @@
|
|
1
|
+
require 'adapters/simple_adapter'
|
2
|
+
# load the ldap facade only if NOT loaded before
|
3
|
+
require 'ldap/ldap_facade' unless Object.const_defined?('Ldap') and Ldap.const_defined?('LdapFacade')
|
4
|
+
|
5
|
+
module Ldap
|
6
|
+
|
7
|
+
# the class provides two ways of getting a LdapFacade. either
|
8
|
+
# one which is put on the current Thread or a new one
|
9
|
+
class LdapConnection
|
10
|
+
|
11
|
+
include ::Slf4r::Logger
|
12
|
+
|
13
|
+
def initialize(uri)
|
14
|
+
@ldaps = { }
|
15
|
+
auth = {
|
16
|
+
:method => :simple,
|
17
|
+
:username => uri[:bind_name],
|
18
|
+
:password => uri[:password]
|
19
|
+
}
|
20
|
+
@config = {
|
21
|
+
:host => uri[:host],
|
22
|
+
:port => uri[:port].to_i,
|
23
|
+
:auth => auth,
|
24
|
+
:base => uri[:base]
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
# puts a LdapFacade into the current thread and executes the
|
29
|
+
# given block.
|
30
|
+
def open
|
31
|
+
begin
|
32
|
+
Ldap::LdapFacade.open(@config) do |ldap|
|
33
|
+
@ldaps[Thread.current] = Ldap::LdapFacade.new(ldap)
|
34
|
+
yield
|
35
|
+
end
|
36
|
+
ensure
|
37
|
+
@ldaps[Thread.current] = nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Ldap::LdapFacade]
|
42
|
+
# either the one from the current Thread or a new one
|
43
|
+
def current
|
44
|
+
ldap = @ldaps[Thread.current]
|
45
|
+
if ldap
|
46
|
+
ldap
|
47
|
+
else
|
48
|
+
Ldap::LdapFacade.new(@config)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
require "dm-core"
|
55
|
+
module DataMapper
|
56
|
+
module Adapters
|
57
|
+
class LdapAdapter < SimpleAdapter
|
58
|
+
|
59
|
+
# @return [Ldap::LdapFacade]
|
60
|
+
# ready to use LdapFacade
|
61
|
+
def ldap
|
62
|
+
@ldap_connection.current
|
63
|
+
end
|
64
|
+
|
65
|
+
def open_ldap_connection(&block)
|
66
|
+
@ldap_connection.open(&block)
|
67
|
+
end
|
68
|
+
|
69
|
+
def key_properties(resource)
|
70
|
+
resource.send(:key_properties).first
|
71
|
+
end
|
72
|
+
|
73
|
+
# helper to remove datamapper specific classes from the conditions
|
74
|
+
# @param [Array] conditions
|
75
|
+
# array of tuples: (action, property, new value)
|
76
|
+
# @return [Array]
|
77
|
+
# tuples: (action, attribute name, new value)
|
78
|
+
def to_ldap_conditions(conditions)
|
79
|
+
ldap_conditions = []
|
80
|
+
conditions.each do |c|
|
81
|
+
ldap_conditions << [c[0], c[1].field, c[2]]
|
82
|
+
end
|
83
|
+
ldap_conditions
|
84
|
+
end
|
85
|
+
|
86
|
+
public
|
87
|
+
|
88
|
+
def initialize(name, uri_or_options)
|
89
|
+
super(name, uri_or_options)
|
90
|
+
@ldap_connection = ::Ldap::LdapConnection.new(@uri)
|
91
|
+
end
|
92
|
+
|
93
|
+
# @param [DataMapper::Resource] resource
|
94
|
+
# to be created
|
95
|
+
# @see SimpleAdapter#create_resource
|
96
|
+
# @return [Fixnum]
|
97
|
+
# value for the primary key or nil
|
98
|
+
def create_resource(resource)
|
99
|
+
logger.debug { resource.inspect }
|
100
|
+
|
101
|
+
props = resource.model.ldap_properties(resource)
|
102
|
+
key = nil
|
103
|
+
resource.send(:properties).each do |prop|
|
104
|
+
value = prop.get!(resource)
|
105
|
+
props[prop.field.to_sym] = value.to_s unless value.nil?
|
106
|
+
key = prop if prop.serial?
|
107
|
+
end
|
108
|
+
key_value = ldap.create_object(resource.model.dn_prefix(resource),
|
109
|
+
resource.model.treebase,
|
110
|
+
key_properties(resource).field,
|
111
|
+
props, resource.model.multivalue_field)
|
112
|
+
logger.debug { "key value: #{key_value.inspect}" }
|
113
|
+
if key_value
|
114
|
+
key.set!(resource, key_value.to_i)
|
115
|
+
resource
|
116
|
+
elsif resource.model.multivalue_field
|
117
|
+
multivalue_prop = resource.send(:properties).detect do |prop|
|
118
|
+
prop.field.to_sym == resource.model.multivalue_field
|
119
|
+
end
|
120
|
+
update_resource(resource,
|
121
|
+
{ multivalue_prop =>
|
122
|
+
resource.send(resource.model.multivalue_field)})
|
123
|
+
else
|
124
|
+
nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# @param [DataMapper::Resource] resource
|
129
|
+
# to be updated
|
130
|
+
# @param [Hash] attributes
|
131
|
+
# new attributes for the resource
|
132
|
+
# @see SimpleAdapter#update_resource
|
133
|
+
def update_resource(resource, attributes)
|
134
|
+
actions = attributes.collect do |property, value|
|
135
|
+
field = property.field.to_sym #TODO sym needed or string ???
|
136
|
+
if resource.model.multivalue_field == property.name
|
137
|
+
if value.nil?
|
138
|
+
[:delete, field, resource.original_values[property.name].to_s]
|
139
|
+
else
|
140
|
+
[:add, field, value.to_s]
|
141
|
+
end
|
142
|
+
elsif value.nil?
|
143
|
+
[:delete, field, []]
|
144
|
+
elsif resource.original_values[property.name].nil?
|
145
|
+
[:add, field, value.to_s]
|
146
|
+
else
|
147
|
+
[:replace, field, value.to_s]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
#puts "actions"
|
151
|
+
#p actions
|
152
|
+
#puts
|
153
|
+
ldap.update_object(resource.model.dn_prefix(resource),
|
154
|
+
resource.model.treebase,
|
155
|
+
actions)
|
156
|
+
end
|
157
|
+
|
158
|
+
# @param [DataMapper::Resource] resource
|
159
|
+
# to be deleted
|
160
|
+
# @see SimpleAdapter#delete_resource
|
161
|
+
def delete_resource(resource)
|
162
|
+
if resource.model.multivalue_field
|
163
|
+
# set the original value so update does the right thing
|
164
|
+
resource.send("#{resource.model.multivalue_field}=".to_sym, nil)
|
165
|
+
update_resource(resource,
|
166
|
+
{ resource.send(:properties)[resource.model.multivalue_field] => nil })
|
167
|
+
else
|
168
|
+
ldap.delete_object(resource.model.dn_prefix(resource),
|
169
|
+
resource.model.treebase)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# @param [DataMapper::Query] query
|
174
|
+
# the search criteria
|
175
|
+
# @return [DataMapper::Resource]
|
176
|
+
# the found resource or nil
|
177
|
+
# @see SimpleAdapter#read_resource
|
178
|
+
def read_resource(query)
|
179
|
+
|
180
|
+
result = ldap.read_objects(query.model.treebase,
|
181
|
+
query.model.key.collect { |k| k.field},
|
182
|
+
to_ldap_conditions(query.conditions))
|
183
|
+
if query.model.multivalue_field
|
184
|
+
resource = result.detect do |item|
|
185
|
+
# run over all values of the multivalue field
|
186
|
+
item[query.model.multivalue_field].any? do |value|
|
187
|
+
values = query.fields.collect do |f|
|
188
|
+
if query.model.multivalue_field == f.field.to_sym
|
189
|
+
value
|
190
|
+
else
|
191
|
+
item[f.field.to_sym].first
|
192
|
+
end
|
193
|
+
end
|
194
|
+
resource = query.model.load(values, query)
|
195
|
+
return resource if filter_resource(resource, query.conditions)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
else
|
199
|
+
values = result.first
|
200
|
+
if values
|
201
|
+
query.fields.collect do |f|
|
202
|
+
values[f.field.to_sym].first
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# @param [DataMapper::Query] query
|
209
|
+
# the search criteria
|
210
|
+
# @return [Array<DataMapper::Resource]
|
211
|
+
# the array of found resources
|
212
|
+
# @see SimpleAdapter#read_resources
|
213
|
+
def read_resources(query)
|
214
|
+
result = ldap.read_objects(query.model.treebase,
|
215
|
+
query.model.key.collect { |k| k.field },
|
216
|
+
to_ldap_conditions(query.conditions))
|
217
|
+
if query.model.multivalue_field
|
218
|
+
props_result = []
|
219
|
+
result.each do |props|
|
220
|
+
# run over all values of the multivalue field
|
221
|
+
props[query.model.multivalue_field].each do |value|
|
222
|
+
values = query.fields.collect do |f|
|
223
|
+
if query.model.multivalue_field == f.field.to_sym
|
224
|
+
value
|
225
|
+
else
|
226
|
+
props[f.field.to_sym].first
|
227
|
+
end
|
228
|
+
end
|
229
|
+
resource = query.model.load(values, query)
|
230
|
+
props_result << resource if filter_resource(resource, query.conditions)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
props_result
|
234
|
+
else # no multivalue field
|
235
|
+
result.collect do |props|
|
236
|
+
query.fields.collect do |f|
|
237
|
+
props[f.field.to_sym].first
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require "dm-core"
|
2
|
+
require 'adapters/simple_adapter'
|
3
|
+
|
4
|
+
# this and the SimpleAdapter is basically the
|
5
|
+
# dm-core/adapter/in_memory_adapter.rb most credits go dm-core.
|
6
|
+
# there are few bug fixes and enhancements.
|
7
|
+
module DataMapper
|
8
|
+
module Adapters
|
9
|
+
class MemoryAdapter < SimpleAdapter
|
10
|
+
|
11
|
+
public
|
12
|
+
|
13
|
+
# @see SimpleAdapter
|
14
|
+
def initialize(name, uri_or_options)
|
15
|
+
super
|
16
|
+
|
17
|
+
@records = Hash.new { |hash,model| hash[model] = Array.new }
|
18
|
+
@ids = Hash.new { |hash,model| hash[model] = 0 }
|
19
|
+
end
|
20
|
+
|
21
|
+
# @see SimpleAdapter
|
22
|
+
def create_resource(resource)
|
23
|
+
uniques = resource.model.properties.select do |prop|
|
24
|
+
prop if prop.unique_index
|
25
|
+
end
|
26
|
+
uniques.each do |prop|
|
27
|
+
raise PersistentError.new "#{prop.name} = #{prop.get(resource)} already exists" unless resource.model.first( prop.name => prop.get(resource) ).nil?
|
28
|
+
end
|
29
|
+
@records[resource.model] << resource
|
30
|
+
resource.id = (@ids[resource.model] += 1) if resource.key.size == 1
|
31
|
+
# and resource.key[0].serial?
|
32
|
+
resource
|
33
|
+
end
|
34
|
+
|
35
|
+
# @see SimpleAdapter
|
36
|
+
def update_resource(resource, attributes)
|
37
|
+
attributes.each do |property, value|
|
38
|
+
property.set!(resource, value)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @see SimpleAdapter
|
43
|
+
def delete_resource(resource)
|
44
|
+
records = @records[resource.model]
|
45
|
+
records.delete(resource)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @see SimpleAdapter
|
49
|
+
def read_resource(query)
|
50
|
+
read(query, false)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @see SimpleAdapter
|
54
|
+
def read_resources(query)
|
55
|
+
read(query, true)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# helper to read either one or many resources matching the given query
|
61
|
+
def read(query, many)
|
62
|
+
model = query.model
|
63
|
+
conditions = query.conditions
|
64
|
+
|
65
|
+
match_with = many ? :select : :detect
|
66
|
+
|
67
|
+
result = @records[model].send(match_with) do |resource|
|
68
|
+
filter_resource(resource, conditions)
|
69
|
+
end
|
70
|
+
|
71
|
+
# TODO Sort
|
72
|
+
|
73
|
+
# TODO Limit
|
74
|
+
|
75
|
+
return result
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require "dm-core"
|
2
|
+
require 'slf4r'
|
3
|
+
|
4
|
+
module DataMapper
|
5
|
+
module Adapters
|
6
|
+
class NoopTransaction
|
7
|
+
|
8
|
+
def close ; end
|
9
|
+
def begin ; end
|
10
|
+
def prepare ; end
|
11
|
+
def commit ; end
|
12
|
+
def rollback ; end
|
13
|
+
def rollback_prepared ; end
|
14
|
+
|
15
|
+
end
|
16
|
+
class SimpleAdapter < AbstractAdapter
|
17
|
+
|
18
|
+
include Slf4r::Logger
|
19
|
+
|
20
|
+
# @see AbstractAdapter
|
21
|
+
def transaction_primitive
|
22
|
+
NoopTransaction.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(name, uri_or_options)
|
26
|
+
super(name, uri_or_options)
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
# checks whether a given resource fullfils the conditions
|
32
|
+
# @param [DataMapper::Resource] resource
|
33
|
+
# @param [Array<Condition>] conditions
|
34
|
+
# @return [Boolean]
|
35
|
+
# true if the resource are within the conditions otherwise false
|
36
|
+
def filter_resource(resource, conditions)
|
37
|
+
#puts "condi"
|
38
|
+
#p conditions
|
39
|
+
# no conditation => no filter
|
40
|
+
if conditions.size == 0
|
41
|
+
true
|
42
|
+
else
|
43
|
+
conditions.all? do |tuple|
|
44
|
+
operator, property, bind_value = *tuple
|
45
|
+
|
46
|
+
value = property.get!(resource)
|
47
|
+
case operator
|
48
|
+
when :eql, :in then equality_comparison(bind_value, value)
|
49
|
+
when :not then !equality_comparison(bind_value, value)
|
50
|
+
when :like then Regexp.new(bind_value.gsub(/%/, ".*")) =~ value
|
51
|
+
when :gt then !value.nil? && value > bind_value
|
52
|
+
when :gte then !value.nil? && value >= bind_value
|
53
|
+
when :lt then !value.nil? && value < bind_value
|
54
|
+
when :lte then !value.nil? && value <= bind_value
|
55
|
+
else raise "Invalid query operator: #{operator.inspect}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# helper method to dispatch the equality test for different
|
62
|
+
# classes
|
63
|
+
def equality_comparison(bind_value, value)
|
64
|
+
case bind_value
|
65
|
+
when Array, Range then bind_value.include?(value)
|
66
|
+
when NilClass then value.nil?
|
67
|
+
else bind_value == value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
public
|
72
|
+
|
73
|
+
# @see AbstractAdapter
|
74
|
+
# @param [Array<DataMapper::Resources>] resources
|
75
|
+
# aaaa
|
76
|
+
# @return [Fixnum]
|
77
|
+
# number of the newly created resources
|
78
|
+
def create(resources)
|
79
|
+
resources.select do |resource|
|
80
|
+
|
81
|
+
create_resource(resource)
|
82
|
+
|
83
|
+
end.size # just return the number of create resources
|
84
|
+
end
|
85
|
+
|
86
|
+
# @see AbstractAdapter
|
87
|
+
# @param [Hash] attributes
|
88
|
+
# collection of attribute, i.e. the name/value pairs which
|
89
|
+
# needs to be updated
|
90
|
+
# @param [Query]
|
91
|
+
# on all resources which are selected by that query the
|
92
|
+
# update will be applied
|
93
|
+
# @return [Fixnum]
|
94
|
+
# number of the updated resources
|
95
|
+
def update(attributes, query)
|
96
|
+
read_many(query).select do |resource|
|
97
|
+
|
98
|
+
update_resource(resource, attributes)
|
99
|
+
|
100
|
+
end.size
|
101
|
+
end
|
102
|
+
|
103
|
+
# @see AbstractAdapter
|
104
|
+
# @param [DataMapper::Query] query
|
105
|
+
# which selects the resource
|
106
|
+
# @return [DataMapper::Resource]
|
107
|
+
# the found Resource or nil
|
108
|
+
def read_one(query)
|
109
|
+
result = read_resource(query)
|
110
|
+
if result.is_a? Resource
|
111
|
+
result
|
112
|
+
elsif result # assume result to be Array with the values
|
113
|
+
#puts "------------------"
|
114
|
+
#p result
|
115
|
+
query.model.load(result, query)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# @see AbstractAdapter
|
120
|
+
# @param [DataMapper::Query] query
|
121
|
+
# which selects the resources
|
122
|
+
# @return [DataMapper::Collection]
|
123
|
+
# collection of Resources
|
124
|
+
def read_many(query)
|
125
|
+
Collection.new(query) do |set|
|
126
|
+
result = read_resources(query)
|
127
|
+
#puts "read_many"
|
128
|
+
#p result
|
129
|
+
if result.size > 0 and result.first.is_a? Resource
|
130
|
+
set.replace(result)
|
131
|
+
else
|
132
|
+
result.each do |values|
|
133
|
+
set.load(values)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# @see AbstractAdapter
|
140
|
+
# @param [Query] query
|
141
|
+
# which selects the resources to be deleted
|
142
|
+
# @return [Fixnum]
|
143
|
+
# number of the deleted resources
|
144
|
+
def delete(query)
|
145
|
+
read_many(query).each do |resource|
|
146
|
+
|
147
|
+
delete_resource(resource)
|
148
|
+
|
149
|
+
end.size
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
# @param [DataMapper::Resource] resource
|
155
|
+
# which will be created
|
156
|
+
# @return [DataMapper::Resource]
|
157
|
+
# either the resource itself if the creation was successful or nil
|
158
|
+
def create_resource(resource)
|
159
|
+
raise NotImplementedError.new
|
160
|
+
end
|
161
|
+
|
162
|
+
# @param [DataMapper::Query] query
|
163
|
+
# which selects the resource
|
164
|
+
# @return [DataMapper::Resource,Array<String>]
|
165
|
+
# the resource or a set of values ordered in the same manner as query.fields attributes
|
166
|
+
def read_resource(query)
|
167
|
+
raise NotImplementedError.new
|
168
|
+
end
|
169
|
+
|
170
|
+
# @param [DataMapper::Query] query
|
171
|
+
# which selects the resources
|
172
|
+
# @return [Array<DataMapper::Resource>,Array<String>]
|
173
|
+
# resources or ordered values
|
174
|
+
# @see #read_resource
|
175
|
+
def read_resources(query)
|
176
|
+
raise NotImplementedError.new
|
177
|
+
end
|
178
|
+
|
179
|
+
# @param [DataMapper::Resource] resource
|
180
|
+
# which will be updated with the given attributes.
|
181
|
+
# @param [Hash] attributes
|
182
|
+
# the keys are the property names and the values are the new values of that property.
|
183
|
+
# @return [DataMapper::Resource]
|
184
|
+
# resource on success otherwise nil
|
185
|
+
def update_resource(resource, attributes)
|
186
|
+
raise NotImplementedError.new
|
187
|
+
end
|
188
|
+
|
189
|
+
# @param [DataMapper::Resource] resource
|
190
|
+
# which will be deleted
|
191
|
+
# @return [DataMapper::Resource]
|
192
|
+
# either the resource if the deletion was successful or nil
|
193
|
+
def delete_resource(resource)
|
194
|
+
raise NotImplementedError.new
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|