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/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