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.
@@ -0,0 +1,60 @@
1
+ require 'slf4r/logger'
2
+ require 'ldap/digest'
3
+
4
+ # dummy implementation which turns the extra ldap configuration noops
5
+ module DataMapper
6
+ module Resource
7
+
8
+ module ClassMethods
9
+
10
+ include ::Slf4r::Logger
11
+
12
+ def ldap_properties(resource = nil, &block)
13
+ if block
14
+ @ldap_properties = block
15
+ elsif resource.instance_of? Hash
16
+ @ldap_properties = resource
17
+ logger.debug { "ldap_properties=#{@ldap_properties.inspect}" }
18
+ elsif resource
19
+ logger.debug { "ldap_properties=#{@ldap_properties.call(resource).inspect}" }
20
+ else
21
+ logger.debug { "ldap_properties=#{@ldap_properties.inspect}" }
22
+ end
23
+ end
24
+
25
+ def treebase(resource = nil)
26
+ if block
27
+ @treebase = block
28
+ elsif resource.instance_of? Hash
29
+ @treebase = resource
30
+ logger.debug { "treebase=#{@treebase.inspect}" }
31
+ elsif resource
32
+ logger.debug { "treebase=#{@treebase.call(resource).inspect}" }
33
+ else
34
+ logger.debug { "treebase=#{treebase}" }
35
+ end
36
+ end
37
+
38
+ def dn_prefix(resource = nil, &block)
39
+ if block
40
+ @dn_prefix = block
41
+ elsif resource.instance_of? Hash
42
+ @dn_prefix = resource
43
+ logger.debug { "dn_prefix=#{@dn_prefix.inspect}" }
44
+ elsif resource
45
+ logger.debug { "dn_prefix=#{@dn_prefix.call(resource).inspect}" }
46
+ else
47
+ logger.debug { "dn_prefix=#{dn_prefix}" }
48
+ end
49
+ end
50
+
51
+ def multivalue_field(field = nil)
52
+ logger.debug { "multivalue_field = #{field}" } if field
53
+ end
54
+ end
55
+
56
+ def authenticate(password)
57
+ raise "NotImplemented"
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,23 @@
1
+ module Ldap
2
+ class Digest
3
+ # method from openldap faq which produces the userPassword attribute
4
+ # for the ldap
5
+ # @param secret String the password
6
+ # @param salt String the salt for the password digester
7
+ # @return the encoded password/salt
8
+ def self.ssha(secret, salt)
9
+ require 'sha1'
10
+ require 'base64'
11
+ (salt.empty? ? "{SHA}": "{SSHA}") +
12
+ Base64.encode64(::Digest::SHA1.digest(secret + salt) + salt).gsub(/\n/, '')
13
+ end
14
+
15
+ # method from openldap faq which produces the userPassword attribute
16
+ # for the ldap
17
+ # @param secret String the password
18
+ # @return the encoded password
19
+ def self.sha(secret)
20
+ ssha(secret, "")
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,210 @@
1
+ require "net/ldap.rb"
2
+ module Ldap
3
+ class LdapFacade
4
+
5
+ # @param config Hash for the ldap connection
6
+ def self.open(config)
7
+ Net::LDAP.open( config ) do |ldap|
8
+ yield ldap
9
+ end
10
+ end
11
+
12
+ include ::Slf4r::Logger
13
+
14
+ # @param config Hash for the ldap connection
15
+ def initialize(config)
16
+ if config.is_a? Hash
17
+ @ldap = Net::LDAP.new( config )
18
+ else
19
+ @ldap = config
20
+ end
21
+ end
22
+
23
+ # @param dn_prefix String the prefix of the dn
24
+ # @param treebase the treebase of the dn or any search
25
+ # @param key_field field which carries the integer unique id of the entity
26
+ # @param props Hash of the ldap attributes of the new ldap object
27
+ # @return nil in case of an error or the new id of the created object
28
+ def create_object(dn_prefix, treebase, key_field, props, silence = false)
29
+ base = "#{treebase},#{@ldap.base}"
30
+ id_sym = key_field.downcase.to_sym
31
+ max = 0
32
+ @ldap.search( :base => base,
33
+ :attributes => [key_field],
34
+ :return_result => false ) do |entry|
35
+ n = entry[id_sym].first.to_i
36
+ max = n if max < n
37
+ end
38
+ id = max + 1
39
+ props[id_sym] = "#{id}"
40
+ if @ldap.add( :dn => dn(dn_prefix, treebase),
41
+ :attributes => props)
42
+ id
43
+ else
44
+ unless silence
45
+ msg = ldap_error("create",
46
+ dn(dn_prefix, treebase)) + "\n\t#{props.inspect}"
47
+ # TODO maybe raise always an error
48
+ if @ldap.get_operation_result.code == 68
49
+ raise ::DataMapper::PersistenceError.new(msg)
50
+ else
51
+ logger.warn(msg)
52
+ end
53
+ end
54
+ nil
55
+ end
56
+ end
57
+
58
+ # @param treebase the treebase of the search
59
+ # @param key_fields Array of fields which carries the integer unique id(s) of the entity
60
+ # @param Array of conditions for the search
61
+ # @return Array of Hashes with a name/values pair for each attribute
62
+ def read_objects(treebase, key_fields, conditions)
63
+ filters = []
64
+ conditions.each do |cond|
65
+ c = cond[2]
66
+ case cond[0]
67
+ when :eql
68
+ if c.nil?
69
+ f = ~ Net::LDAP::Filter.pres( cond[1].to_s )
70
+ elsif c.class == Array
71
+ f = nil
72
+ c.each do |cc|
73
+ if f
74
+ f = f | Net::LDAP::Filter.eq( cond[1].to_s, cc.to_s )
75
+ else
76
+ f = Net::LDAP::Filter.eq( cond[1].to_s, cc.to_s )
77
+ end
78
+ end
79
+ #elsif c.class == Range
80
+ # p c
81
+ # 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 )
82
+ else
83
+ f = Net::LDAP::Filter.eq( cond[1].to_s, c.to_s )
84
+ end
85
+ when :gte
86
+ f = Net::LDAP::Filter.ge( cond[1].to_s, c.to_s )
87
+ when :lte
88
+ f = Net::LDAP::Filter.le( cond[1].to_s, c.to_s )
89
+ when :not
90
+ if c.nil?
91
+ f = Net::LDAP::Filter.pres( cond[1].to_s )
92
+ elsif c.class == Array
93
+ f = nil
94
+ c.each do |cc|
95
+ if f
96
+ f = f | Net::LDAP::Filter.eq( cond[1].to_s, cc.to_s )
97
+ else
98
+ f = Net::LDAP::Filter.eq( cond[1].to_s, cc.to_s )
99
+ end
100
+ end
101
+ f = ~ f
102
+ else
103
+ f = ~ Net::LDAP::Filter.eq( cond[1].to_s, c.to_s )
104
+ end
105
+ when :like
106
+ f = Net::LDAP::Filter.eq( cond[1].to_s, c.to_s.gsub(/%/, "*").gsub(/_/, "*").gsub(/\*\*/, "*") )
107
+ else
108
+ logger.error(cond[0].to_s + " needs coding")
109
+ end
110
+ filters << f
111
+ end
112
+
113
+ filter = nil
114
+ filters.each do |f|
115
+ if filter.nil?
116
+ filter = f
117
+ else
118
+ filter = filter & f
119
+ end
120
+ end
121
+ logger.debug { "search filter: (#{filter.to_s})" }
122
+ result = []
123
+ @ldap.search( :base => "#{treebase},#{@ldap.base}",
124
+ :filter => filter ) do |res|
125
+ map = to_map(res)
126
+ #puts map[key_field.to_sym]
127
+ # TODO maybe make filter which removes this unless
128
+ # TODO move this into the ldap_Adapter to make it more general, so that
129
+ # all field with Integer gets converted, etc
130
+ result << map if key_fields.select do |key_field|
131
+ if map.member? key_field.to_sym
132
+ # convert field to integer
133
+ map[key_field.to_sym] = [map[key_field.to_sym].collect { |k| k.to_i != 0 ? k.to_s : k }].flatten
134
+ true
135
+ end
136
+ end.size > 0 # i.e. there was at least one key_field in the map
137
+ end
138
+ result
139
+ end
140
+
141
+
142
+ # @param dn_prefix String the prefix of the dn
143
+ # @param treebase the treebase of the dn or any search
144
+ # @param actions the add/replace/delete actions on the attributes
145
+ # @return nil in case of an error or true
146
+ def update_object(dn_prefix, treebase, actions)
147
+ if @ldap.modify( :dn => dn(dn_prefix, treebase),
148
+ :operations => actions )
149
+ true
150
+ else
151
+ logger.warn(ldap_error("update",
152
+ dn(dn_prefix, treebase) + "\n\t#{actions.inspect}"))
153
+ nil
154
+ end
155
+ end
156
+
157
+ # @param dn_prefix String the prefix of the dn
158
+ # @param treebase the treebase of the dn or any search
159
+ # @return nil in case of an error or true
160
+ def delete_object(dn_prefix, treebase)
161
+ if @ldap.delete( :dn => dn(dn_prefix, treebase) )
162
+ true
163
+ else
164
+ logger.warn(ldap_error("delete",
165
+ dn(dn_prefix, treebase)))
166
+
167
+ nil
168
+ end
169
+ end
170
+
171
+
172
+ # @param dn String for identifying the ldap object
173
+ # @param password String to be used for authenticate to the dn
174
+ def authenticate(dn, password)
175
+ Net::LDAP.new( { :host => @ldap.host,
176
+ :port => @ldap.port,
177
+ :auth => {
178
+ :method => :simple,
179
+ :username => dn,
180
+ :password => password
181
+ },
182
+ :base => @ldap.base
183
+ } ).bind
184
+ end
185
+
186
+ # helper to concat the dn from the various parts
187
+ # @param dn_prefix String the prefix of the dn
188
+ # @param treebase the treebase of the dn or any search
189
+ # @return the complete dn String
190
+ def dn(dn_prefix, treebase)
191
+ "#{dn_prefix},#{treebase},#{@ldap.base}"
192
+ end
193
+
194
+ private
195
+
196
+ # helper to extract the Hash from the ldap search result
197
+ # @param Entry from the ldap_search
198
+ # @return Hash with name/value pairs of the entry
199
+ def to_map(entry)
200
+ def entry.map
201
+ @myhash
202
+ end
203
+ entry.map
204
+ end
205
+
206
+ def ldap_error(method, dn)
207
+ "#{method} error: (#{@ldap.get_operation_result.code}) #{@ldap.get_operation_result.message}\n\tDN: #{dn}"
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,56 @@
1
+ module Ldap
2
+ class LdapFacade
3
+
4
+ def self.open(config)
5
+ puts "open"
6
+ p config
7
+ puts
8
+ yield "dummy"
9
+ end
10
+
11
+ def initialize(uri)
12
+ puts "new #{self.hash}"
13
+ p uri
14
+ puts
15
+ end
16
+
17
+ def create_object(treebase, dn_prefix, key_field, props, silence = false)
18
+ options = { :dn_prefix => dn_prefix,
19
+ :treebase => treebase,
20
+ :key_field => key_field,
21
+ :properties => props }
22
+ puts "create #{self.hash}"
23
+ p options
24
+ puts
25
+ @@count ||= 0
26
+ @@count += 1
27
+ end
28
+
29
+ def read_objects(treebase, key_field, conditions, many = false)
30
+ options = { :treebase => treebase,
31
+ :key_field => key_field,
32
+ :conditions => conditions, :many => many }
33
+ puts "read #{self.hash}"
34
+ p options
35
+ puts
36
+ [] if many
37
+ end
38
+
39
+ def update_object(treebase, dn_prefix, actions)
40
+ options = { :dn_prefix => dn_prefix,
41
+ :treebase => treebase,
42
+ :actions => actions }
43
+ puts "update #{self.hash}"
44
+ p options
45
+ puts
46
+ end
47
+
48
+ def delete_object(treebase, dn_prefix)
49
+ options = { :dn_prefix => dn_prefix,
50
+ :treebase => treebase }
51
+ puts "delete #{self.hash}"
52
+ p options
53
+ puts
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,3 @@
1
+ module Ldap
2
+ VERSION = '0.2.0'
3
+ end
@@ -0,0 +1,107 @@
1
+ require 'ldap/digest'
2
+
3
+ module DataMapper
4
+ module Resource
5
+ module ClassMethods
6
+
7
+ # if called without parameter or block the given properties get returned.
8
+ # if called with a block then the block gets stored. if called with new
9
+ # properties they get stored. if called with a Resource then either the
10
+ # stored block gets called with that Resource or the stored properties get
11
+ # returned.
12
+ # @param [Hash,DataMapper::Resource] properties_or_resource either a Hash with properties, a Resource or nil
13
+ # @param [block] &block to be stored for later calls when properties_or_resource is nil
14
+ # @return [Hash] when called with a Resource
15
+ def ldap_properties(properties_or_resource = nil, &block)
16
+ if properties_or_resource
17
+ if properties_or_resource.instance_of? Hash
18
+ @ldap_properties = properties_or_resource
19
+ elsif @ldap_properties.instance_of? Hash
20
+ @ldap_properties
21
+ else
22
+ @ldap_properties.call(properties_or_resource)
23
+ end
24
+ else
25
+ @ldap_properties = block
26
+ end
27
+ end
28
+
29
+ # if called without parameter or block the given treebase gets returned.
30
+ # if called with a block then the block gets stored. if called with a
31
+ # String then it gets stored. if called with a Resource then either the
32
+ # stored block gets called with that Resource or the stored String gets
33
+ # returned.
34
+ # @param [String,DataMapper::Resource] treebase_or_resource either a String, a Resource or nil
35
+ # @param [block] &block to be stored for later calls when base_or_resource is nil
36
+ # @return [String] when called with a Resource
37
+ def treebase(base_or_resource = nil, &block)
38
+ if base_or_resource
39
+ if base_or_resource.instance_of? String
40
+ @treebase = base_or_resource
41
+ elsif @treebase.instance_of? String
42
+ @treebase
43
+ else
44
+ @treebase.call(base_or_resource)
45
+ end
46
+ else
47
+ if block
48
+ @treebase = block
49
+ else # backwards compatibility
50
+ @treebase
51
+ end
52
+ end
53
+ end
54
+
55
+ # if called without parameter or block the given dn_prefix gets returned.
56
+ # if called with a block then the block gets stored. if called with a
57
+ # String then it gets stored. if called with a Resource then either the
58
+ # stored block gets called with that Resource or the stored String gets
59
+ # returned.
60
+ # @param [String,DataMapper::Resource] prefix_or_resource either a String, a Resource or nil
61
+ # @param [&block] block to be stored for later calls
62
+ # @return [String, nil] when called with a Resource
63
+ def dn_prefix(prefix_or_resource = nil, &block)
64
+ if prefix_or_resource
65
+ if prefix_or_resource.instance_of? String
66
+ @ldap_dn = prefix_or_resource
67
+ elsif @ldap_dn.instance_of? String
68
+ @ldap_dn
69
+ else
70
+ @ldap_dn.call(prefix_or_resource)
71
+ end
72
+ else
73
+ @ldap_dn = block
74
+ end
75
+ end
76
+
77
+ # if called without parameter then the stored field gets returned
78
+ # otherwise the given parameters gets stored
79
+ # @param [Symbol, String] field a new multivalue_field
80
+ # @return [Symbol] the multivalue_field
81
+ def multivalue_field(field = nil)
82
+ if field.nil?
83
+ @ldap_multivalue_field
84
+ else
85
+ @ldap_multivalue_field = field.to_sym
86
+ end
87
+ end
88
+ end
89
+
90
+ # authenticate the current resource against the stored password
91
+ # @param [String] password to authenticate
92
+ # @return [TrueClass, FalseClass] whether password was right or wrong
93
+ def authenticate(password)
94
+ ldap.authenticate(ldap.dn(self.class.dn_prefix(self),
95
+ self.class.treebase),
96
+ password)
97
+ end
98
+
99
+ private
100
+ # short cut to the ldap facade
101
+ # @return [Ldap::LdapFacade]
102
+ def ldap
103
+ raise "not an ldap adapter #{repository.adapter.name}" unless repository.adapter.respond_to? :ldap
104
+ repository.adapter.ldap
105
+ end
106
+ end
107
+ end