dm-ldap-adapter 0.2.0

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