dm-ldap-adapter 0.3.1 → 0.3.2

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 CHANGED
@@ -1,3 +1,19 @@
1
+ version 0.3.2
2
+ =============
3
+
4
+ * lazy property are not loaded from ldap anymore
5
+
6
+ * new facade which uses ruby-ldap, since it has better support for ldap protocol and is about 30% faster with search queries
7
+
8
+ version 0.3.1
9
+ =============
10
+
11
+ * fixed LdapArray bug in collections
12
+
13
+ * default of LdapArray is now a new object for each resource instance
14
+
15
+ * allow Serial to be used in dn_prefix
16
+
1
17
  version 0.3.0
2
18
  =============
3
19
 
data/Manifest.txt CHANGED
@@ -1,6 +1,7 @@
1
1
  History.txt
2
2
  MIT-LICENSE
3
3
  Manifest.txt
4
+ Manifest.txt-
4
5
  README-example.markdown
5
6
  README.txt
6
7
  Rakefile
@@ -13,9 +14,12 @@ lib/adapters/simple_adapter.rb
13
14
  lib/dummy_ldap_resource.rb
14
15
  lib/ldap/array.rb
15
16
  lib/ldap/digest.rb
16
- lib/ldap/ldap_facade.rb
17
+ lib/ldap/net_ldap_facade.rb
18
+ lib/ldap/ruby_ldap_facade.rb
17
19
  lib/ldap/version.rb
18
20
  lib/ldap_resource.rb
21
+ net-ldap.txt
22
+ ruby-ldap.txt
19
23
  spec/assiociations_ldap_adapter_spec.rb
20
24
  spec/authentication_ldap_adapter_spec.rb
21
25
  spec/ldap_adapter_spec.rb
data/Manifest.txt- ADDED
@@ -0,0 +1,27 @@
1
+ History.txt
2
+ MIT-LICENSE
3
+ Manifest.txt
4
+ README-example.markdown
5
+ README.txt
6
+ Rakefile
7
+ example/identity_map.rb
8
+ example/posix.rb
9
+ ldap-commands.txt
10
+ lib/adapters/ldap_adapter.rb
11
+ lib/adapters/memory_adapter.rb
12
+ lib/adapters/simple_adapter.rb
13
+ lib/dummy_ldap_resource.rb
14
+ lib/ldap/array.rb
15
+ lib/ldap/digest.rb
16
+ lib/ldap/ldap_facade.rb
17
+ lib/ldap/version.rb
18
+ lib/ldap_resource.rb
19
+ spec/assiociations_ldap_adapter_spec.rb
20
+ spec/authentication_ldap_adapter_spec.rb
21
+ spec/ldap_adapter_spec.rb
22
+ spec/multi_repository_spec.rb
23
+ spec/multi_value_attributes_spec.rb
24
+ spec/spec.opts
25
+ spec/spec_helper.rb
26
+ test.db
27
+ test.ldif
data/README.txt CHANGED
@@ -88,7 +88,17 @@ and
88
88
 
89
89
  gives the same result when *all* names are `NULL` !!!
90
90
 
91
- OR conditions can be done with :conditions option but only of the form "<property_name> <comparator> <value> [or <property_name> <comparator> <value>]*" where the comparator is one of "=", "like"
91
+ === OR conditions
92
+
93
+ or-conditions can be done with :conditions option but only of the form "<property_name> <comparator> <value> [or <property_name> <comparator> <value>]*" where the comparator is one of "=", "like". it can be also combined with extra ANDs like this example
94
+
95
+ Contact.all(:name.like => "A%", :conditions => ["phone like '+49%' or mobile like '+49%'"])
96
+
97
+ === using the ruby-ldap gem
98
+
99
+ just require the right facade before require the adapter:
100
+
101
+ require 'ldap/ruby_ldap_facade'
92
102
 
93
103
  === multiple repositories
94
104
 
data/example/posix.rb CHANGED
@@ -19,6 +19,10 @@ dummy = true #uncomment this to use dummy, i.e. a database instead of ldap
19
19
  dummy = false # uncomment this to use ldap
20
20
  unless dummy
21
21
  require 'ldap_resource'
22
+
23
+ # comment this out if you want to use "net/ldap"
24
+ require 'ldap/ruby_ldap_facade'
25
+
22
26
  require 'adapters/ldap_adapter'
23
27
 
24
28
  DataMapper.setup(:default, {
@@ -1,6 +1,6 @@
1
1
  require 'adapters/simple_adapter'
2
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')
3
+ require 'ldap/net_ldap_facade' unless Object.const_defined?('Ldap') and Ldap.const_defined?('LdapFacade')
4
4
 
5
5
  module Ldap
6
6
 
@@ -131,10 +131,15 @@ module DataMapper
131
131
  key_properties(resource).field)
132
132
  resource_dup.send("#{key_properties(resource).name}=".to_sym, id)
133
133
  props[key_properties(resource).field.to_sym] = "#{id}"
134
- key_value = ldap.create_object(resource.model.dn_prefix(resource_dup),
135
- resource.model.treebase,
136
- key_properties(resource).field,
137
- props, resource.model.multivalue_field)
134
+ key_value = begin
135
+ ldap.create_object(resource.model.dn_prefix(resource_dup),
136
+ resource.model.treebase,
137
+ key_properties(resource).field,
138
+ props, resource.model.multivalue_field)
139
+ rescue => e
140
+ raise e unless resource.model.multivalue_field
141
+ # TODO something with creating these multivalue objects
142
+ end
138
143
  logger.debug { "resource #{resource.inspect} key value: #{key_value.inspect}" + ", multivalue_field: " + resource.model.multivalue_field.to_s }
139
144
  if key_value and !key.nil?
140
145
  key.set!(resource, key_value.to_i)
@@ -225,9 +230,11 @@ module DataMapper
225
230
  # the found resource or nil
226
231
  # @see SimpleAdapter#read_resource
227
232
  def read_resource(query)
233
+ field_names = query.fields.collect {|f| f.field }
228
234
  result = ldap.read_objects(query.model.treebase,
229
235
  query.model.key.collect { |k| k.field},
230
- to_ldap_conditions(query))
236
+ to_ldap_conditions(query),
237
+ field_names)
231
238
  if query.model.multivalue_field
232
239
  resource = result.detect do |item|
233
240
  # run over all values of the multivalue field
@@ -265,14 +272,16 @@ module DataMapper
265
272
  # the array of found resources
266
273
  # @see SimpleAdapter#read_resources
267
274
  def read_resources(query)
275
+ field_names = query.fields.collect {|f| f.field }
268
276
  result = ldap.read_objects(query.model.treebase,
269
277
  query.model.key.collect { |k| k.field },
270
- to_ldap_conditions(query))
278
+ to_ldap_conditions(query),
279
+ field_names)
271
280
  if query.model.multivalue_field
272
281
  props_result = []
273
282
  result.each do |props|
274
283
  # run over all values of the multivalue field
275
- props[query.model.multivalue_field].each do |value|
284
+ (props[query.model.multivalue_field] || []).each do |value|
276
285
  values = query.fields.collect do |f|
277
286
  if query.model.multivalue_field == f.field.to_sym
278
287
  value
@@ -63,7 +63,7 @@ module Ldap
63
63
  # @param key_fields Array of fields which carries the integer unique id(s) of the entity
64
64
  # @param Array of conditions for the search
65
65
  # @return Array of Hashes with a name/values pair for each attribute
66
- def read_objects(treebase, key_fields, conditions)
66
+ def read_objects(treebase, key_fields, conditions, field_names)
67
67
  filters = []
68
68
  conditions.each do |cond|
69
69
  c = cond[2]
@@ -146,6 +146,7 @@ module Ldap
146
146
  logger.debug { "search filter: (#{filter.to_s})" }
147
147
  result = []
148
148
  @ldap.search( :base => "#{treebase},#{@ldap.base}",
149
+ :attributes => field_names,
149
150
  :filter => filter ) do |res|
150
151
  map = to_map(res)
151
152
  #puts map[key_field.to_sym]
@@ -0,0 +1,274 @@
1
+ require 'ldap'
2
+ require 'net/ldap'
3
+ require 'slf4r'
4
+
5
+ module Ldap
6
+ class Connection < LDAP::Conn
7
+
8
+ attr_reader :base, :host, :port
9
+
10
+ def initialize(config)
11
+ super(config[:host], config[:port])
12
+ @base = config[:base]
13
+ @port = config[:port]
14
+ @host = config[:host]
15
+ set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
16
+ end
17
+
18
+ end
19
+
20
+ class LdapFacade
21
+
22
+ # @param config Hash for the ldap connection
23
+ def self.open(config)
24
+ ldap2 = Connection.new(config)
25
+ ldap2.bind(config[:auth][:username], config[:auth][:password]) do |ldap|
26
+ yield ldap
27
+ end
28
+ end
29
+
30
+ include ::Slf4r::Logger
31
+
32
+ # @param config Hash for the ldap connection
33
+ def initialize(config)
34
+ if config.is_a? Hash
35
+ @ldap2 = Connection.new(config)
36
+ @ldap2.bind(config[:auth][:username], config[:auth][:password])
37
+ else
38
+ @ldap2 = config
39
+ end
40
+ end
41
+
42
+ def retrieve_next_id(treebase, key_field)
43
+ max = 0
44
+ @ldap2.search("#{treebase},#{@ldap2.base}",
45
+ LDAP::LDAP_SCOPE_SUBTREE,
46
+ "(objectclass=*)",
47
+ [key_field]) do |entry|
48
+ n = (entry.vals(key_field) || [0]).first.to_i
49
+ max = n if max < n
50
+ end
51
+ max + 1
52
+ end
53
+
54
+ # @param dn_prefix String the prefix of the dn
55
+ # @param treebase the treebase of the dn or any search
56
+ # @param key_field field which carries the integer unique id of the entity
57
+ # @param props Hash of the ldap attributes of the new ldap object
58
+ # @return nil in case of an error or the new id of the created object
59
+ def create_object(dn_prefix, treebase, key_field, props, silence = false)
60
+ base = "#{treebase},#{@ldap2.base}"
61
+ mods = props.collect do |k,v|
62
+ LDAP.mod(LDAP::LDAP_MOD_ADD, k.to_s, v.is_a?(Array) ? v : [v.to_s] )
63
+ end
64
+ if @ldap2.add( dn(dn_prefix, treebase), mods)
65
+ # :attributes => props) and @ldap.get_operation_result.code.to_s == "0"
66
+ props[key_field.downcase.to_sym]
67
+ else
68
+ unless silence
69
+ msg = ldap_error("create",
70
+ dn(dn_prefix, treebase)) + "\n\t#{props.inspect}"
71
+ # TODO maybe raise always an error
72
+ if @ldap2.get_operation_result.code.to_s == "68"
73
+ raise ::DataMapper::PersistenceError.new(msg)
74
+ else
75
+ logger.warn(msg)
76
+ end
77
+ end
78
+ nil
79
+ end
80
+ end
81
+
82
+ # @param treebase the treebase of the search
83
+ # @param key_fields Array of fields which carries the integer unique id(s) of the entity
84
+ # @param Array of conditions for the search
85
+ # @return Array of Hashes with a name/values pair for each attribute
86
+ def read_objects(treebase, key_fields, conditions, field_names)
87
+ filters = []
88
+ conditions.each do |cond|
89
+ c = cond[2]
90
+ case cond[0]
91
+ when :or_operator
92
+ f = nil
93
+ cond[1].each do |cc|
94
+ ff = case cc[0]
95
+ when :eql
96
+ Net::LDAP::Filter.eq( cc[1].to_s, cc[2].to_s )
97
+ when :gte
98
+ f = Net::LDAP::Filter.ge( cc[1].to_s, cc[2].to_s )
99
+ when :lte
100
+ f = Net::LDAP::Filter.le( cc[1].to_s, cc[2].to_s )
101
+ when :like
102
+ f = Net::LDAP::Filter.eq( cc[1].to_s, cc[2].to_s.gsub(/%/, "*").gsub(/_/, "*").gsub(/\*\*/, "*") )
103
+ else
104
+ logger.error(cc[0].to_s + " needs coding")
105
+ end
106
+ if f
107
+ f = f | ff
108
+ else
109
+ f = ff
110
+ end
111
+ end
112
+ when :eql
113
+ if c.nil?
114
+ f = ~ Net::LDAP::Filter.pres( cond[1].to_s )
115
+ elsif c.class == Array
116
+ f = nil
117
+ c.each do |cc|
118
+ if f
119
+ f = f | Net::LDAP::Filter.eq( cond[1].to_s, cc.to_s )
120
+ else
121
+ f = Net::LDAP::Filter.eq( cond[1].to_s, cc.to_s )
122
+ end
123
+ end
124
+ #elsif c.class == Range
125
+ # p c
126
+ # 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 )
127
+ else
128
+ f = Net::LDAP::Filter.eq( cond[1].to_s, c.to_s )
129
+ end
130
+ when :gte
131
+ f = Net::LDAP::Filter.ge( cond[1].to_s, c.to_s )
132
+ when :lte
133
+ f = Net::LDAP::Filter.le( cond[1].to_s, c.to_s )
134
+ when :not
135
+ if c.nil?
136
+ f = Net::LDAP::Filter.pres( cond[1].to_s )
137
+ elsif c.class == Array
138
+ f = nil
139
+ c.each do |cc|
140
+ if f
141
+ f = f | Net::LDAP::Filter.eq( cond[1].to_s, cc.to_s )
142
+ else
143
+ f = Net::LDAP::Filter.eq( cond[1].to_s, cc.to_s )
144
+ end
145
+ end
146
+ f = ~ f
147
+ else
148
+ f = ~ Net::LDAP::Filter.eq( cond[1].to_s, c.to_s )
149
+ end
150
+ when :like
151
+ f = Net::LDAP::Filter.eq( cond[1].to_s, c.to_s.gsub(/%/, "*").gsub(/_/, "*").gsub(/\*\*/, "*") )
152
+ else
153
+ logger.error(cond[0].to_s + " needs coding")
154
+ end
155
+ filters << f if f
156
+ end
157
+
158
+ filter = nil
159
+ filters.each do |f|
160
+ if filter.nil?
161
+ filter = f
162
+ else
163
+ filter = filter & f
164
+ end
165
+ end
166
+ logger.debug { "search filter: (#{filter.to_s})" }
167
+ result = []
168
+ begin
169
+ @ldap2.search("#{treebase},#{@ldap2.base}",
170
+ LDAP::LDAP_SCOPE_SUBTREE,
171
+ filter.to_s == "" ? "(objectclass=*)" : filter.to_s.gsub(/\(\(/, "(").gsub(/\)\)/, ")"),
172
+ field_names) do |res|
173
+
174
+ map = to_map(res)
175
+ #puts map[key_field.to_sym]
176
+ # TODO maybe make filter which removes this unless
177
+ # TODO move this into the ldap_Adapter to make it more general, so that
178
+ # all field with Integer gets converted, etc
179
+ result << map if key_fields.select do |key_field|
180
+ if map.member? key_field.to_sym
181
+ # convert field to integer
182
+ map[key_field.to_sym] = [map[key_field.to_sym].collect { |k| k.to_i != 0 ? k.to_s : k }].flatten
183
+ true
184
+ end
185
+ end.size > 0 # i.e. there was at least one key_field in the map
186
+ end
187
+ rescue RuntimeError => e
188
+ raise e unless e.message == "no result returned by search"
189
+ end
190
+ result
191
+ end
192
+
193
+
194
+ # @param dn_prefix String the prefix of the dn
195
+ # @param treebase the treebase of the dn or any search
196
+ # @param actions the add/replace/delete actions on the attributes
197
+ # @return nil in case of an error or true
198
+ def update_object(dn_prefix, treebase, actions)
199
+ mods = actions.collect do |act|
200
+ mod_op = case act[0]
201
+ when :add
202
+ LDAP::LDAP_MOD_ADD
203
+ when :replace
204
+ LDAP::LDAP_MOD_REPLACE
205
+ when :delete
206
+ LDAP::LDAP_MOD_DELETE
207
+ end
208
+ LDAP.mod(mod_op, act[1].to_s, act[2] == [] ? [] : [act[2].to_s])
209
+ end
210
+ if @ldap2.modify( dn(dn_prefix, treebase),
211
+ mods )
212
+ true
213
+ else
214
+ logger.warn(ldap_error("update",
215
+ dn(dn_prefix, treebase) + "\n\t#{actions.inspect}"))
216
+ nil
217
+ end
218
+ end
219
+
220
+ # @param dn_prefix String the prefix of the dn
221
+ # @param treebase the treebase of the dn or any search
222
+ # @return nil in case of an error or true
223
+ def delete_object(dn_prefix, treebase)
224
+ if @ldap2.delete( dn(dn_prefix, treebase) )
225
+ true
226
+ else
227
+ logger.warn(ldap_error("delete",
228
+ dn(dn_prefix, treebase)))
229
+
230
+ nil
231
+ end
232
+ end
233
+
234
+
235
+ # @param dn String for identifying the ldap object
236
+ # @param password String to be used for authenticate to the dn
237
+ def authenticate(dn, password)
238
+ Net::LDAP.new( { :host => @ldap2.host,
239
+ :port => @ldap2.port,
240
+ :auth => {
241
+ :method => :simple,
242
+ :username => dn,
243
+ :password => password
244
+ },
245
+ :base => @ldap2.base
246
+ } ).bind
247
+ end
248
+
249
+ # helper to concat the dn from the various parts
250
+ # @param dn_prefix String the prefix of the dn
251
+ # @param treebase the treebase of the dn or any search
252
+ # @return the complete dn String
253
+ def dn(dn_prefix, treebase)
254
+ "#{dn_prefix},#{treebase},#{@ldap2.base}"
255
+ end
256
+
257
+ private
258
+
259
+ # helper to extract the Hash from the ldap search result
260
+ # @param Entry from the ldap_search
261
+ # @return Hash with name/value pairs of the entry
262
+ def to_map(entry)
263
+ map = {}
264
+ LDAP::entry2hash(entry).each do |k,v|
265
+ map[k.downcase.to_sym] = v
266
+ end
267
+ map
268
+ end
269
+
270
+ def ldap_error(method, dn)
271
+ "#{method} error: (#{@ldap2.get_operation_result.code}) #{@ldap2.get_operation_result.message}\n\tDN: #{dn}"
272
+ end
273
+ end
274
+ end
data/lib/ldap/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ldap
2
- VERSION = '0.3.1'
2
+ VERSION = '0.3.2'
3
3
  end
data/net-ldap.txt ADDED
@@ -0,0 +1,11 @@
1
+ 6.225829
2
+ 6.410389
3
+ 6.462378
4
+ 6.227339
5
+ 6.324937
6
+ 6.55648
7
+ 6.331228
8
+ 6.729877
9
+ 6.416464
10
+ 6.99718
11
+ 0
data/ruby-ldap.txt ADDED
@@ -0,0 +1,11 @@
1
+ 4.534209
2
+ 4.659179
3
+ 4.688091
4
+ 4.721425
5
+ 4.495823
6
+ 4.260121
7
+ 4.656991
8
+ 4.024788
9
+ 4.444355
10
+ 4.283845
11
+ 0
@@ -6,7 +6,7 @@ class Contact
6
6
 
7
7
  property :id, Serial, :field => "uidnumber"
8
8
  property :login, String, :field => "uid", :unique_index => true
9
- property :hashed_password, String, :field => "userpassword", :access => :private
9
+ property :hashed_password, String, :field => "userpassword", :access => :private, :lazy => true
10
10
  property :name, String, :field => "cn"
11
11
  property :mail, LdapArray
12
12
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dm-ldap-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - mkristian
@@ -64,10 +64,13 @@ extra_rdoc_files:
64
64
  - Manifest.txt
65
65
  - README.txt
66
66
  - ldap-commands.txt
67
+ - net-ldap.txt
68
+ - ruby-ldap.txt
67
69
  files:
68
70
  - History.txt
69
71
  - MIT-LICENSE
70
72
  - Manifest.txt
73
+ - Manifest.txt-
71
74
  - README-example.markdown
72
75
  - README.txt
73
76
  - Rakefile
@@ -80,9 +83,12 @@ files:
80
83
  - lib/dummy_ldap_resource.rb
81
84
  - lib/ldap/array.rb
82
85
  - lib/ldap/digest.rb
83
- - lib/ldap/ldap_facade.rb
86
+ - lib/ldap/net_ldap_facade.rb
87
+ - lib/ldap/ruby_ldap_facade.rb
84
88
  - lib/ldap/version.rb
85
89
  - lib/ldap_resource.rb
90
+ - net-ldap.txt
91
+ - ruby-ldap.txt
86
92
  - spec/assiociations_ldap_adapter_spec.rb
87
93
  - spec/authentication_ldap_adapter_spec.rb
88
94
  - spec/ldap_adapter_spec.rb