dm-ldap-adapter 0.3.1 → 0.3.2

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