dm-ldap-adapter 0.2.0

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