dm-ldap-adapter 0.4.3-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,201 @@
1
+ require 'ldap'
2
+ require 'slf4r'
3
+ require 'ldap/conditions_2_filter'
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 RubyLdapFacade
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
+ props[key_field.to_sym]
66
+ else
67
+ unless silence
68
+ msg = ldap_error("create",
69
+ dn(dn_prefix, treebase)) + "\n\t#{props.inspect}"
70
+ # TODO maybe raise always an error
71
+ if @ldap2.get_operation_result.code.to_s == "68"
72
+ raise ::DataMapper::PersistenceError.new(msg)
73
+ else
74
+ logger.warn(msg)
75
+ end
76
+ end
77
+ nil
78
+ end
79
+ end
80
+
81
+ # @param treebase the treebase of the search
82
+ # @param key_fields Array of fields which carries the integer unique id(s) of the entity
83
+ # @param Array of conditions for the search
84
+ # @return Array of Hashes with a name/values pair for each attribute
85
+ def read_objects(treebase, key_fields, conditions, field_names, order_field = '')
86
+
87
+ if !conditions.nil? and conditions.size > 0
88
+ filter = Conditions2Filter.convert(conditions).to_s
89
+ else
90
+ filter = "(objectclass=*)"
91
+ end
92
+
93
+ result = []
94
+ begin
95
+ @ldap2.search("#{treebase},#{@ldap2.base}",
96
+ LDAP::LDAP_SCOPE_SUBTREE,
97
+ filter,
98
+ field_names, false, 0, 0, order_field) do |res|
99
+ mapp = to_map(field_names, res)
100
+ # TODO maybe make filter which removes this unless
101
+ # TODO move this into the ldap_Adapter to make it more general, so that
102
+ # all field with Integer gets converted, etc
103
+ # NOTE: somehow the fields are downcase coming from query.model
104
+ result << mapp if key_fields.detect do |key_field|
105
+ mapp.keys.detect {|k| k.to_s.downcase == key_field.downcase }
106
+ end
107
+ end
108
+ end
109
+ result
110
+ end
111
+
112
+
113
+ # @param dn_prefix String the prefix of the dn
114
+ # @param treebase the treebase of the dn or any search
115
+ # @param actions the add/replace/delete actions on the attributes
116
+ # @return nil in case of an error or true
117
+ def update_object(dn_prefix, treebase, actions)
118
+ mods = actions.collect do |act|
119
+ mod_op = case act[0]
120
+ when :add
121
+ LDAP::LDAP_MOD_ADD
122
+ when :replace
123
+ LDAP::LDAP_MOD_REPLACE
124
+ when :delete
125
+ LDAP::LDAP_MOD_DELETE
126
+ end
127
+ LDAP.mod(mod_op, act[1].to_s, act[2] == [] ? [] : [act[2].to_s])
128
+ end
129
+ if @ldap2.modify( dn(dn_prefix, treebase),
130
+ mods )
131
+ true
132
+ else
133
+ logger.warn(ldap_error("update",
134
+ dn(dn_prefix, treebase) + "\n\t#{actions.inspect}"))
135
+ nil
136
+ end
137
+ end
138
+
139
+ # @param dn_prefix String the prefix of the dn
140
+ # @param treebase the treebase of the dn or any search
141
+ # @return nil in case of an error or true
142
+ def delete_object(dn_prefix, treebase)
143
+ if @ldap2.delete( dn(dn_prefix, treebase) )
144
+ true
145
+ else
146
+ logger.warn(ldap_error("delete",
147
+ dn(dn_prefix, treebase)))
148
+
149
+ nil
150
+ end
151
+ end
152
+
153
+
154
+ # @param dn String for identifying the ldap object
155
+ # @param password String to be used for authenticate to the dn
156
+ def authenticate(dn, password)
157
+ bound = false
158
+ ldap_con = LDAP::Conn.new(@ldap2.host, @ldap2.port)
159
+ ldap_con.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 )
160
+ begin
161
+ ldap_con.bind(dn, password, LDAP::LDAP_AUTH_SIMPLE) do
162
+ bound = true
163
+ end
164
+ rescue LDAP::ResultError => msg
165
+ if msg.to_s =~ /Invalid\ credentials/i
166
+ logger.info("Invalid Credentials: #{dn}")
167
+ else
168
+ logger.warn "Authentication Error: #{msg.to_s}"
169
+ end
170
+ end
171
+ bound
172
+ end
173
+
174
+ # helper to concat the dn from the various parts
175
+ # @param dn_prefix String the prefix of the dn
176
+ # @param treebase the treebase of the dn or any search
177
+ # @return the complete dn String
178
+ def dn(dn_prefix, treebase)
179
+ "#{dn_prefix},#{treebase},#{@ldap2.base}"
180
+ end
181
+
182
+ private
183
+
184
+ # helper to extract the Hash from the ldap search result
185
+ # @param Entry from the ldap_search
186
+ # @return Hash with name/value pairs of the entry
187
+ def to_map(field_names, entry)
188
+ fields = {:dn => :dn}
189
+ field_names.each { |f| fields[f.downcase.to_sym] = f.to_sym }
190
+ map = {}
191
+ LDAP::entry2hash(entry).each do |k,v|
192
+ map[fields[k.downcase.to_sym]] = v
193
+ end
194
+ map
195
+ end
196
+
197
+ def ldap_error(method, dn)
198
+ "#{method} error: (#{@ldap2.get_operation_result.code}) #{@ldap2.get_operation_result.message}\n\tDN: #{dn}"
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,2 @@
1
+ require 'dm-transactions'
2
+ require 'adapters/noop_transaction'
@@ -0,0 +1,188 @@
1
+ require 'ldap'
2
+ require 'slf4r'
3
+ require 'ldap/conditions_2_filter'
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 RubyLdapFacade
21
+
22
+ # @param config Hash for the ldap connection
23
+ def self.open(config)
24
+ ldap3 = com.unboundid.ldap.sdk.LDAPConnection.new(config[:host], config[:port], config[:base] + "." + config[:auth][:username], config[:auth][:password])
25
+
26
+ yield ldap
27
+ end
28
+
29
+ include ::Slf4r::Logger
30
+
31
+ # @param config Hash for the ldap connection
32
+ def initialize(config)
33
+ if config.is_a? Hash
34
+ @ldap2 = Connection.new(config)
35
+ @ldap2.bind(config[:auth][:username], config[:auth][:password])
36
+ else
37
+ @ldap2 = config
38
+ end
39
+ end
40
+
41
+ def retrieve_next_id(treebase, key_field)
42
+ max = 0
43
+ @ldap2.search("#{treebase},#{@ldap2.base}",
44
+ LDAP::LDAP_SCOPE_SUBTREE,
45
+ "(objectclass=*)",
46
+ [key_field]) do |entry|
47
+ n = (entry.vals(key_field) || [0]).first.to_i
48
+ max = n if max < n
49
+ end
50
+ max + 1
51
+ end
52
+
53
+ # @param dn_prefix String the prefix of the dn
54
+ # @param treebase the treebase of the dn or any search
55
+ # @param key_field field which carries the integer unique id of the entity
56
+ # @param props Hash of the ldap attributes of the new ldap object
57
+ # @return nil in case of an error or the new id of the created object
58
+ def create_object(dn_prefix, treebase, key_field, props, silence = false)
59
+ base = "#{treebase},#{@ldap2.base}"
60
+ mods = props.collect do |k,v|
61
+ LDAP.mod(LDAP::LDAP_MOD_ADD, k.to_s, v.is_a?(::Array) ? v : [v.to_s] )
62
+ end
63
+ if @ldap2.add( dn(dn_prefix, treebase), mods)
64
+ # :attributes => props) and @ldap.get_operation_result.code.to_s == "0"
65
+ props[key_field.downcase.to_sym]
66
+ else
67
+ unless silence
68
+ msg = ldap_error("create",
69
+ dn(dn_prefix, treebase)) + "\n\t#{props.inspect}"
70
+ # TODO maybe raise always an error
71
+ if @ldap2.get_operation_result.code.to_s == "68"
72
+ raise ::DataMapper::PersistenceError.new(msg)
73
+ else
74
+ logger.warn(msg)
75
+ end
76
+ end
77
+ nil
78
+ end
79
+ end
80
+
81
+ # @param treebase the treebase of the search
82
+ # @param key_fields Array of fields which carries the integer unique id(s) of the entity
83
+ # @param Array of conditions for the search
84
+ # @return Array of Hashes with a name/values pair for each attribute
85
+ def read_objects(treebase, key_fields, conditions, field_names, order_field = '')
86
+ filter = Conditions2Filter.convert(conditions)
87
+ result = []
88
+ begin
89
+ @ldap2.search("#{treebase},#{@ldap2.base}",
90
+ LDAP::LDAP_SCOPE_SUBTREE,
91
+ filter.to_s == "" ? "(objectclass=*)" : filter.to_s.gsub(/\(\(/, "(").gsub(/\)\)/, ")"),
92
+ field_names, false, 0, 0, order_field) do |res|
93
+
94
+ map = to_map(res)
95
+ #puts map[key_field.to_sym]
96
+ # TODO maybe make filter which removes this unless
97
+ # TODO move this into the ldap_Adapter to make it more general, so that
98
+ # all field with Integer gets converted, etc
99
+ result << map if key_fields.detect do |key_field|
100
+ map.member? key_field.to_sym
101
+ end
102
+ end
103
+ end
104
+ result
105
+ end
106
+
107
+
108
+ # @param dn_prefix String the prefix of the dn
109
+ # @param treebase the treebase of the dn or any search
110
+ # @param actions the add/replace/delete actions on the attributes
111
+ # @return nil in case of an error or true
112
+ def update_object(dn_prefix, treebase, actions)
113
+ mods = actions.collect do |act|
114
+ mod_op = case act[0]
115
+ when :add
116
+ LDAP::LDAP_MOD_ADD
117
+ when :replace
118
+ LDAP::LDAP_MOD_REPLACE
119
+ when :delete
120
+ LDAP::LDAP_MOD_DELETE
121
+ end
122
+ LDAP.mod(mod_op, act[1].to_s, act[2] == [] ? [] : [act[2].to_s])
123
+ end
124
+ if @ldap2.modify( dn(dn_prefix, treebase),
125
+ mods )
126
+ true
127
+ else
128
+ logger.warn(ldap_error("update",
129
+ dn(dn_prefix, treebase) + "\n\t#{actions.inspect}"))
130
+ nil
131
+ end
132
+ end
133
+
134
+ # @param dn_prefix String the prefix of the dn
135
+ # @param treebase the treebase of the dn or any search
136
+ # @return nil in case of an error or true
137
+ def delete_object(dn_prefix, treebase)
138
+ if @ldap2.delete( dn(dn_prefix, treebase) )
139
+ true
140
+ else
141
+ logger.warn(ldap_error("delete",
142
+ dn(dn_prefix, treebase)))
143
+
144
+ nil
145
+ end
146
+ end
147
+
148
+
149
+ # @param dn String for identifying the ldap object
150
+ # @param password String to be used for authenticate to the dn
151
+ def authenticate(dn, password)
152
+ Net::LDAP.new( { :host => @ldap2.host,
153
+ :port => @ldap2.port,
154
+ :auth => {
155
+ :method => :simple,
156
+ :username => dn,
157
+ :password => password
158
+ },
159
+ :base => @ldap2.base
160
+ } ).bind
161
+ end
162
+
163
+ # helper to concat the dn from the various parts
164
+ # @param dn_prefix String the prefix of the dn
165
+ # @param treebase the treebase of the dn or any search
166
+ # @return the complete dn String
167
+ def dn(dn_prefix, treebase)
168
+ "#{dn_prefix},#{treebase},#{@ldap2.base}"
169
+ end
170
+
171
+ private
172
+
173
+ # helper to extract the Hash from the ldap search result
174
+ # @param Entry from the ldap_search
175
+ # @return Hash with name/value pairs of the entry
176
+ def to_map(entry)
177
+ map = {}
178
+ LDAP::entry2hash(entry).each do |k,v|
179
+ map[k.downcase.to_sym] = v
180
+ end
181
+ map
182
+ end
183
+
184
+ def ldap_error(method, dn)
185
+ "#{method} error: (#{@ldap2.get_operation_result.code}) #{@ldap2.get_operation_result.message}\n\tDN: #{dn}"
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,3 @@
1
+ module Ldap
2
+ VERSION = '0.4.0.alpha1'.freeze
3
+ end
@@ -0,0 +1,189 @@
1
+ require 'ldap/digest'
2
+ require 'dm-core'
3
+ require 'ldap/array'
4
+
5
+ module DataMapper
6
+ module Model
7
+
8
+ def load(records, query)
9
+ repository = query.repository
10
+ repository_name = repository.name
11
+ fields = query.fields
12
+ discriminator = properties(repository_name).discriminator
13
+ no_reload = !query.reload?
14
+
15
+ field_map = {}
16
+ fields.each { |property| field_map[property] = property.field }
17
+
18
+ records.map do |record|
19
+ identity_map = nil
20
+ key_values = nil
21
+ resource = nil
22
+
23
+ case record
24
+ when Hash
25
+ # remap fields to use the Property object
26
+ record = record.dup
27
+ field_map.each { |property, field| record[property] = record.delete(field) if record.key?(field) }
28
+
29
+ model = discriminator && discriminator.load(record[discriminator]) || self
30
+ model_key = model.key(repository_name)
31
+
32
+ resource = if model_key.valid?(key_values = record.values_at(*model_key))
33
+ identity_map = repository.identity_map(model)
34
+ identity_map[key_values]
35
+ end
36
+
37
+ resource ||= model.allocate
38
+
39
+ fields.each do |property|
40
+ next if no_reload && property.loaded?(resource)
41
+
42
+ value = record[property]
43
+
44
+ value = property.load(value)
45
+
46
+ property.set!(resource, value)
47
+ end
48
+
49
+ when Resource
50
+ model = record.model
51
+ model_key = model.key(repository_name)
52
+
53
+ resource = if model_key.valid?(key_values = record.key)
54
+ identity_map = repository.identity_map(model)
55
+ identity_map[key_values]
56
+ end
57
+
58
+ resource ||= model.allocate
59
+
60
+ fields.each do |property|
61
+ next if no_reload && property.loaded?(resource)
62
+
63
+ property.set!(resource, property.get!(record))
64
+ end
65
+ end
66
+
67
+ resource.instance_variable_set(:@_repository, repository)
68
+
69
+ if identity_map
70
+ resource.persisted_state = Resource::State::Clean.new(resource) unless resource.persisted_state?
71
+
72
+ # defer setting the IdentityMap so second level caches can
73
+ # record the state of the resource after loaded
74
+ identity_map[key_values] = resource
75
+ else
76
+ resource.persisted_state = Resource::State::Immutable.new(resource)
77
+ end
78
+
79
+ resource
80
+ end
81
+ end
82
+
83
+ module LdapResource
84
+
85
+ Model.append_inclusions self
86
+
87
+ # authenticate the current resource against the stored password
88
+ # @param [String] password to authenticate
89
+ # @return [TrueClass, FalseClass] whether password was right or wrong
90
+ def authenticate(password)
91
+ ldap.authenticate(ldap.dn(self.class.dn_prefix(self),
92
+ self.class.treebase),
93
+ password)
94
+ end
95
+
96
+ # if called without parameter or block the given properties get returned.
97
+ # if called with a block then the block gets stored. if called with new
98
+ # properties they get stored. if called with a Resource then either the
99
+ # stored block gets called with that Resource or the stored properties get
100
+ # returned.
101
+ # @param [Hash,DataMapper::Resource] properties_or_resource either a Hash with properties, a Resource or nil
102
+ # @param [block] &block to be stored for later calls when properties_or_resource is nil
103
+ # @return [Hash] when called with a Resource
104
+ def ldap_properties(properties_or_resource = nil, &block)
105
+ if properties_or_resource
106
+ if properties_or_resource.instance_of? Hash
107
+ @ldap_properties = properties_or_resource
108
+ elsif @ldap_properties.instance_of? Hash
109
+ @ldap_properties
110
+ else
111
+ @ldap_properties.call(properties_or_resource)
112
+ end
113
+ else
114
+ @ldap_properties = block
115
+ end
116
+ end
117
+
118
+ # if called without parameter or block the given treebase gets returned.
119
+ # if called with a block then the block gets stored. if called with a
120
+ # String then it gets stored. if called with a Resource then either the
121
+ # stored block gets called with that Resource or the stored String gets
122
+ # returned.
123
+ # @param [String,DataMapper::Resource] treebase_or_resource either a String, a Resource or nil
124
+ # @param [block] &block to be stored for later calls when base_or_resource is nil
125
+ # @return [String] when called with a Resource
126
+ def treebase(base_or_resource = nil, &block)
127
+ if base_or_resource
128
+ if base_or_resource.instance_of? String
129
+ @treebase = base_or_resource
130
+ elsif @treebase.instance_of? String
131
+ @treebase
132
+ else
133
+ @treebase.call(base_or_resource)
134
+ end
135
+ else
136
+ if block
137
+ @treebase = block
138
+ else # backwards compatibility
139
+ @treebase
140
+ end
141
+ end
142
+ end
143
+
144
+ # if called without parameter or block the given dn_prefix gets returned.
145
+ # if called with a block then the block gets stored. if called with a
146
+ # String then it gets stored. if called with a Resource then either the
147
+ # stored block gets called with that Resource or the stored String gets
148
+ # returned.
149
+ # @param [String,DataMapper::Resource] prefix_or_resource either a String, a Resource or nil
150
+ # @param [&block] block to be stored for later calls
151
+ # @return [String, nil] when called with a Resource
152
+ def dn_prefix(prefix_or_resource = nil, &block)
153
+ if prefix_or_resource
154
+ if prefix_or_resource.instance_of? String
155
+ @ldap_dn = prefix_or_resource
156
+ elsif @ldap_dn.instance_of? String
157
+ @ldap_dn
158
+ else
159
+ @ldap_dn.call(prefix_or_resource)
160
+ end
161
+ else
162
+ @ldap_dn = block
163
+ end
164
+ end
165
+
166
+ # if called without parameter then the stored field gets returned
167
+ # otherwise the given parameters gets stored
168
+ # @param [Symbol, String] field a new multivalue_field
169
+ # @return [Symbol] the multivalue_field
170
+ def multivalue_field(field = nil)
171
+ if field.nil?
172
+ @ldap_multivalue_field
173
+ else
174
+ @ldap_multivalue_field = field.to_sym
175
+ end
176
+ end
177
+
178
+ private
179
+ # short cut to the ldap facade
180
+ # @return [Ldap::LdapFacade]
181
+ def ldap
182
+ raise "not an ldap adapter #{repository.adapter.name}" unless repository.adapter.respond_to? :ldap
183
+ repository.adapter.ldap
184
+ end
185
+ end
186
+
187
+ include LdapResource
188
+ end
189
+ end