activeldap 5.2.4 → 6.1.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.
@@ -30,6 +30,8 @@ module ActiveLdap
30
30
  :scope,
31
31
  :sasl_options,
32
32
  :follow_referrals,
33
+ :use_paged_results,
34
+ :page_size,
33
35
  ]
34
36
 
35
37
  @@row_even = true
@@ -41,6 +43,7 @@ module ActiveLdap
41
43
  @bind_tried = false
42
44
  @entry_attributes = {}
43
45
  @follow_referrals = nil
46
+ @page_size = nil
44
47
  @configuration = configuration.dup
45
48
  @logger = @configuration.delete(:logger)
46
49
  @configuration.assert_valid_keys(VALID_ADAPTER_CONFIGURATION_KEYS)
@@ -48,6 +51,7 @@ module ActiveLdap
48
51
  instance_variable_set("@#{name}", configuration[name])
49
52
  end
50
53
  @follow_referrals = true if @follow_referrals.nil?
54
+ @page_size ||= Configuration::DEFAULT_CONFIG[:page_size]
51
55
  @instrumenter = ActiveSupport::Notifications.instrumenter
52
56
  end
53
57
 
@@ -169,23 +173,40 @@ module ActiveLdap
169
173
  end
170
174
 
171
175
  def search(options={})
172
- filter = parse_filter(options[:filter]) || 'objectClass=*'
173
- attrs = options[:attributes] || []
174
- scope = ensure_scope(options[:scope] || @scope)
175
176
  base = options[:base]
177
+ base = ensure_dn_string(base)
178
+ attributes = options[:attributes] || []
179
+ attributes = attributes.to_a # just in case
176
180
  limit = options[:limit] || 0
177
181
  limit = nil if limit <= 0
182
+ use_paged_results = options[:use_paged_results]
183
+ use_paged_results = @use_paged_results if use_paged_results.nil?
184
+ if use_paged_results
185
+ use_paged_results = limit != 1 && supported_control.paged_results?
186
+ end
187
+ search_options = {
188
+ base: base,
189
+ scope: ensure_scope(options[:scope] || @scope),
190
+ filter: parse_filter(options[:filter]) || 'objectClass=*',
191
+ attributes: attributes,
192
+ limit: limit,
193
+ use_paged_results: use_paged_results,
194
+ page_size: options[:page_size] || @page_size,
195
+ }
178
196
 
179
- attrs = attrs.to_a # just in case
180
- base = ensure_dn_string(base)
181
197
  begin
182
198
  operation(options) do
183
- yield(base, scope, filter, attrs, limit)
199
+ yield(search_options)
184
200
  end
185
- rescue LdapError::NoSuchObject, LdapError::InvalidDnSyntax
201
+ rescue LdapError::NoSuchObject, LdapError::InvalidDnSyntax => error
186
202
  # Do nothing on failure
187
203
  @logger.info do
188
- args = [$!.class, $!.message, filter, attrs.inspect]
204
+ args = [
205
+ error.class.class,
206
+ error.message,
207
+ search_options[:filter],
208
+ search_options[:attributes].inspect,
209
+ ]
189
210
  _("Ignore error %s(%s): filter %s: attributes: %s") % args
190
211
  end
191
212
  end
@@ -662,8 +683,7 @@ module ActiveLdap
662
683
  :scope => :base,
663
684
  :attributes => attrs,
664
685
  :limit => 1,
665
- :try_reconnect => try_reconnect,
666
- :use_paged_results => false) do |dn, attributes|
686
+ :try_reconnect => try_reconnect) do |dn, attributes|
667
687
  found_attributes = attributes
668
688
  end
669
689
  found_attributes
@@ -22,9 +22,23 @@ module ActiveLdap
22
22
  super do |host, port, method|
23
23
  uri = construct_uri(host, port, method == :ssl)
24
24
  with_start_tls = method == :start_tls
25
- info = {:uri => uri, :with_start_tls => with_start_tls}
26
- [log("connect", info) {JndiConnection.new(host, port, method, @timeout)},
27
- uri, with_start_tls]
25
+ follow_referrals = follow_referrals?(options)
26
+ info = {
27
+ :uri => uri,
28
+ :with_start_tls => with_start_tls,
29
+ :follow_referrals => follow_referrals,
30
+ }
31
+ [
32
+ log("connect", info) {
33
+ JndiConnection.new(host,
34
+ port,
35
+ method,
36
+ @timeout,
37
+ follow_referrals)
38
+ },
39
+ uri,
40
+ with_start_tls,
41
+ ]
28
42
  end
29
43
  end
30
44
 
@@ -46,12 +60,10 @@ module ActiveLdap
46
60
  end
47
61
 
48
62
  def search(options={}, &block)
49
- super(options) do |base, scope, filter, attrs, limit|
50
- info = {
51
- :base => base, :scope => scope_name(scope), :filter => filter,
52
- :attributes => attrs, :limit => limit,
53
- }
54
- execute(:search, info, base, scope, filter, attrs, limit, &block)
63
+ super(options) do |search_options|
64
+ scope = search_options[:scope]
65
+ info = search_options.merge(scope: scope_name(scope))
66
+ execute(:search, info, search_options, &block)
55
67
  end
56
68
  end
57
69
 
@@ -25,6 +25,8 @@ module ActiveLdap
25
25
  Context = naming.Context
26
26
  StartTlsRequest = ldap.StartTlsRequest
27
27
  Control = ldap.Control
28
+ PagedResultsControl = ldap.PagedResultsControl
29
+ PagedResultsResponseControl = ldap.PagedResultsResponseControl
28
30
 
29
31
  CommunicationException = naming.CommunicationException
30
32
  ServiceUnavailableException = naming.ServiceUnavailableException
@@ -73,13 +75,14 @@ module ActiveLdap
73
75
  end
74
76
  end
75
77
 
76
- def initialize(host, port, method, timeout)
78
+ def initialize(host, port, method, timeout, follow_referrals)
77
79
  @host = host
78
80
  @port = port
79
81
  @method = method
80
82
  @timeout = timeout
81
83
  @context = nil
82
84
  @tls = nil
85
+ @follow_referrals = follow_referrals
83
86
  end
84
87
 
85
88
  def unbind
@@ -108,23 +111,55 @@ module ActiveLdap
108
111
  bound?
109
112
  end
110
113
 
111
- def search(base, scope, filter, attrs, limit)
114
+ def search(options)
115
+ base = options[:base]
116
+ scope = options[:scope]
117
+ filter = options[:filter]
118
+ attributes = options[:attributes]
119
+ limit = options[:limit]
120
+ use_paged_results = options[:use_paged_results]
121
+ page_size = options[:page_size]
122
+
112
123
  controls = SearchControls.new
113
124
  controls.search_scope = scope
114
125
 
115
126
  controls.count_limit = limit if limit
116
- unless attrs.blank?
117
- controls.returning_attributes = attrs.to_java(:string)
127
+ unless attributes.blank?
128
+ controls.returning_attributes = attributes.to_java(:string)
129
+ end
130
+
131
+ page_cookie = nil
132
+ if use_paged_results
133
+ # https://devdocs.io/openjdk~8/javax/naming/ldap/pagedresultscontrol
134
+ @context.set_request_controls([build_paged_results_control(page_size)])
135
+ else
136
+ @context.set_request_controls([])
118
137
  end
119
138
 
120
- @context.search(base, filter, controls).each do |result|
121
- attributes = {}
122
- result.attributes.get_all.each do |attribute|
123
- attributes[attribute.get_id] = attribute.get_all.collect do |value|
124
- value.is_a?(String) ? value : String.from_java_bytes(value)
125
- end
139
+ escaped_base = escape_dn(base)
140
+
141
+ loop do
142
+ @context.search(escaped_base, filter, controls).each do |search_result|
143
+ yield(build_raw_search_result(search_result))
126
144
  end
127
- yield([result.name_in_namespace, attributes])
145
+
146
+ break unless use_paged_results
147
+
148
+ # Find the paged search cookie
149
+ response_controls = @context.get_response_controls
150
+ break unless response_controls
151
+ response_controls.each do |response_control|
152
+ next unless response_control.is_a?(PagedResultsResponseControl)
153
+ page_cookie = response_control.get_cookie
154
+ break
155
+ end
156
+
157
+ break unless page_cookie
158
+
159
+ # Set paged results control so we can keep getting results.
160
+ paged_results_control =
161
+ build_paged_results_control(page_size, page_cookie)
162
+ @context.set_request_controls([paged_results_control])
128
163
  end
129
164
  end
130
165
 
@@ -133,25 +168,32 @@ module ActiveLdap
133
168
  records.each do |record|
134
169
  attributes.put(record.to_java_attribute)
135
170
  end
136
- @context.create_subcontext(dn, attributes)
171
+ @context.set_request_controls([])
172
+ @context.create_subcontext(escape_dn(dn), attributes)
137
173
  end
138
174
 
139
175
  def modify(dn, records)
140
176
  items = records.collect(&:to_java_modification_item)
141
- @context.modify_attributes(dn, items.to_java(ModificationItem))
177
+ @context.set_request_controls([])
178
+ @context.modify_attributes(escape_dn(dn), items.to_java(ModificationItem))
142
179
  end
143
180
 
144
181
  def modify_rdn(dn, new_rdn, delete_old_rdn)
145
182
  # should use mutex
146
183
  delete_rdn_key = "java.naming.ldap.deleteRDN"
147
- @context.add_to_environment(delete_rdn_key, delete_old_rdn.to_s)
148
- @context.rename(dn, new_rdn)
149
- ensure
150
- @context.remove_from_environment(delete_rdn_key)
184
+ @context.set_request_controls([])
185
+ begin
186
+ @context.add_to_environment(delete_rdn_key, delete_old_rdn.to_s)
187
+ @context.rename(escape_dn(dn), escape_dn(new_rdn))
188
+ ensure
189
+ @context.remove_from_environment(delete_rdn_key)
190
+ end
151
191
  end
152
192
 
153
193
  def delete(dn)
154
- @context.destroy_subcontext(dn)
194
+ escaped_dn = escape_dn(dn)
195
+ @context.set_request_controls([])
196
+ @context.destroy_subcontext(escaped_dn)
155
197
  end
156
198
 
157
199
  private
@@ -162,9 +204,10 @@ module ActiveLdap
162
204
  Context::PROVIDER_URL => ldap_uri,
163
205
  'com.sun.jndi.ldap.connect.timeout' => (@timeout * 1000).to_i.to_s,
164
206
  'com.sun.jndi.ldap.read.timeout' => (@timeout * 1000).to_i.to_s,
207
+ 'java.naming.ldap.derefAliases' => 'never',
208
+ 'java.naming.referral' => @follow_referrals ? 'follow' : 'ignore',
165
209
  }
166
- environment = HashTable.new(environment)
167
- context = InitialLdapContext.new(environment, nil)
210
+ context = InitialLdapContext.new(HashTable.new(environment), nil)
168
211
  if @method == :start_tls
169
212
  @tls = context.extended_operation(StartTlsRequest.new)
170
213
  @tls.negotiate
@@ -185,6 +228,26 @@ module ActiveLdap
185
228
  protocol = @method == :ssl ? "ldaps" : "ldap"
186
229
  "#{protocol}://#{@host}:#{@port}/"
187
230
  end
231
+
232
+ def escape_dn(dn)
233
+ javax.naming.ldap.LdapName.new(dn)
234
+ rescue Java::JavaLang::IllegalArgumentException, Java::JavaxNaming::InvalidNameException
235
+ dn
236
+ end
237
+
238
+ def build_paged_results_control(page_size, page_cookie=nil)
239
+ PagedResultsControl.new(page_size, page_cookie, Control::CRITICAL)
240
+ end
241
+
242
+ def build_raw_search_result(search_result)
243
+ attributes = {}
244
+ search_result.attributes.get_all.each do |attribute|
245
+ attributes[attribute.get_id] = attribute.get_all.collect do |value|
246
+ value.is_a?(String) ? value : String.from_java_bytes(value)
247
+ end
248
+ end
249
+ [search_result.name_in_namespace, attributes]
250
+ end
188
251
  end
189
252
  end
190
253
  end
@@ -113,25 +113,11 @@ module ActiveLdap
113
113
  end
114
114
 
115
115
  def search(options={})
116
- super(options) do |base, scope, filter, attrs, limit|
116
+ super(options) do |search_options|
117
117
  begin
118
- use_paged_results = options[:use_paged_results]
119
- if use_paged_results or use_paged_results.nil?
120
- use_paged_results = supported_control.paged_results?
121
- end
122
- info = {
123
- :base => base, :scope => scope_name(scope),
124
- :filter => filter, :attributes => attrs, :limit => limit,
125
- }
126
- options = {
127
- :base => base,
128
- :scope => scope,
129
- :filter => filter,
130
- :attributes => attrs,
131
- :limit => limit,
132
- :use_paged_results => use_paged_results
133
- }
134
- execute(:search_full, info, options) do |entry|
118
+ scope = search_options[:scope]
119
+ info = search_options.merge(scope: scope_name(scope))
120
+ execute(:search_full, info, search_options) do |entry|
135
121
  attributes = {}
136
122
  entry.attrs.each do |attr|
137
123
  value = entry.vals(attr)
@@ -142,7 +128,10 @@ module ActiveLdap
142
128
  rescue RuntimeError
143
129
  if $!.message == "no result returned by search"
144
130
  @logger.debug do
145
- args = [filter, attrs.inspect]
131
+ args = [
132
+ search_options[:filter],
133
+ search_options[:attributes].inspect,
134
+ ]
146
135
  _("No matches: filter: %s: attributes: %s") % args
147
136
  end
148
137
  else
@@ -220,15 +209,17 @@ module ActiveLdap
220
209
  def prepare_connection(options={})
221
210
  operation(options) do
222
211
  @connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
223
- unless follow_referrals?(options)
224
- @connection.set_option(LDAP::LDAP_OPT_REFERRALS, 0)
225
- end
212
+ @ldap_follow_referrals = follow_referrals?(options) ? 1 : 0
213
+ @connection.set_option(LDAP::LDAP_OPT_REFERRALS,
214
+ @ldap_follow_referrals)
226
215
  end
227
216
  end
228
217
 
229
218
  def execute(method, info=nil, *args, &block)
230
219
  begin
231
220
  name = (info || {}).delete(:name) || method
221
+ @connection.set_option(LDAP::LDAP_OPT_REFERRALS,
222
+ @ldap_follow_referrals)
232
223
  log(name, info) {@connection.send(method, *args, &block)}
233
224
  rescue LDAP::ResultError
234
225
  @connection.assert_error_code
@@ -65,12 +65,28 @@ module LDAP
65
65
  attributes = options[:attributes]
66
66
  limit = options[:limit] || 0
67
67
  use_paged_results = options[:use_paged_results]
68
+ page_size = options[:page_size]
68
69
  if @@have_search_ext
69
70
  if use_paged_results
70
- paged_search(base, scope, filter, attributes, limit, &block)
71
+ paged_search(base,
72
+ scope,
73
+ filter,
74
+ attributes,
75
+ limit,
76
+ page_size,
77
+ &block)
71
78
  else
72
- search_ext(base, scope, filter, attributes,
73
- false, nil, nil, 0, 0, limit, &block)
79
+ search_ext(base,
80
+ scope,
81
+ filter,
82
+ attributes,
83
+ false,
84
+ nil,
85
+ nil,
86
+ 0,
87
+ 0,
88
+ limit,
89
+ &block)
74
90
  end
75
91
  else
76
92
  i = 0
@@ -120,27 +136,30 @@ module LDAP
120
136
  end
121
137
  end
122
138
 
123
- def paged_search(base, scope, filter, attributes, limit, &block)
124
- # work around a bug with openldap
125
- page_size = 126
139
+ def paged_search(base, scope, filter, attributes, limit, page_size, &block)
126
140
  cookie = ""
127
141
  critical = true
128
-
129
142
  loop do
130
143
  ber_string = LDAP::Control.encode(page_size, cookie)
131
144
  control = LDAP::Control.new(LDAP::LDAP_CONTROL_PAGEDRESULTS,
132
145
  ber_string,
133
146
  critical)
134
-
135
- search_ext(base, scope, filter, attributes,
136
- false, [control], nil, 0, 0, limit, &block)
147
+ search_ext(base,
148
+ scope,
149
+ filter,
150
+ attributes,
151
+ false,
152
+ [control],
153
+ nil,
154
+ 0,
155
+ 0,
156
+ limit,
157
+ &block)
137
158
 
138
159
  control = find_paged_results_control(@controls)
139
160
  break if control.nil?
140
- returned_size, cookie = control.decode
141
- returned_size = returned_size.to_i
142
- page_size = returned_size if returned_size > 0
143
161
 
162
+ _estimated_result_set_size, cookie = control.decode
144
163
  break if cookie.empty?
145
164
  end
146
165
  end
@@ -69,25 +69,16 @@ module ActiveLdap
69
69
  end
70
70
 
71
71
  def search(options={})
72
- use_paged_results = options[:use_paged_results]
73
- if use_paged_results or use_paged_results.nil?
74
- paged_results_supported = supported_control.paged_results?
75
- else
76
- paged_results_supported = false
77
- end
78
- super(options) do |base, scope, filter, attrs, limit|
72
+ super(options) do |search_options|
73
+ scope = search_options[:scope]
74
+ info = search_options.merge(scope: scope_name(scope))
79
75
  args = {
80
- :base => base,
81
- :scope => scope,
82
- :filter => filter,
83
- :attributes => attrs,
84
- :size => limit,
85
- :paged_searches_supported => paged_results_supported,
86
- }
87
- info = {
88
- :base => base, :scope => scope_name(scope),
89
- :filter => filter, :attributes => attrs, :limit => limit,
90
- :paged_results_supported => paged_results_supported,
76
+ base: search_options[:base],
77
+ scope: scope,
78
+ filter: search_options[:filter],
79
+ attributes: search_options[:attributes],
80
+ size: search_options[:limit],
81
+ paged_searcheds_supported: search_options[:paged_results_supported],
91
82
  }
92
83
  execute(:search, info, args) do |entry|
93
84
  attributes = {}
@@ -851,6 +851,8 @@ module ActiveLdap
851
851
 
852
852
  _schema = _local_entry_attribute = nil
853
853
  targets = sanitize_for_mass_assignment(new_attributes)
854
+ have_dn = false
855
+ dn_value = nil
854
856
  targets.each do |key, value|
855
857
  setter = "#{key}="
856
858
  unless respond_to?(setter)
@@ -860,8 +862,15 @@ module ActiveLdap
860
862
  _local_entry_attribute ||= local_entry_attribute
861
863
  _local_entry_attribute.register(attribute)
862
864
  end
863
- send(setter, value)
865
+ case setter
866
+ when "dn=", "id="
867
+ have_dn = true
868
+ dn_value = value
869
+ else
870
+ send(setter, value)
871
+ end
864
872
  end
873
+ self.dn = dn_value if have_dn
865
874
  end
866
875
 
867
876
  def to_ldif_record
@@ -1281,6 +1290,7 @@ module ActiveLdap
1281
1290
  end
1282
1291
 
1283
1292
  def compute_base
1293
+ ensure_update_dn
1284
1294
  base_of_class = self.class.base
1285
1295
  if @base_value.nil?
1286
1296
  base_of_class
@@ -48,6 +48,15 @@ module ActiveLdap
48
48
  DEFAULT_CONFIG[:retry_on_timeout] = true
49
49
  DEFAULT_CONFIG[:follow_referrals] = true
50
50
 
51
+ # 500 is the default size limit value of OpenLDAP 2.4:
52
+ # https://openldap.org/doc/admin24/limits.html#Global%20Limits
53
+ #
54
+ # We may change this when we find LDAP server that its the default
55
+ # size limit is smaller than 500.
56
+ DEFAULT_CONFIG[:page_size] = 500
57
+ # Whether using paged results if available.
58
+ DEFAULT_CONFIG[:use_paged_results] = true
59
+
51
60
  DEFAULT_CONFIG[:logger] = nil
52
61
 
53
62
  module ClassMethods
@@ -103,8 +112,22 @@ module ActiveLdap
103
112
  end
104
113
  end
105
114
 
115
+ def parent_configuration(target)
116
+ if target.is_a?(Base)
117
+ target = target.class
118
+ else
119
+ target = target.superclass
120
+ end
121
+ while target <= Base
122
+ config = defined_configurations[target.active_connection_key]
123
+ return config if config
124
+ target = target.superclass
125
+ end
126
+ default_configuration
127
+ end
128
+
106
129
  def merge_configuration(user_configuration, target=self)
107
- configuration = default_configuration
130
+ configuration = parent_configuration(target).dup
108
131
  prepare_configuration(user_configuration).each do |key, value|
109
132
  case key
110
133
  when :base
@@ -6,7 +6,7 @@ module ActiveLdap
6
6
 
7
7
  module ClassMethods
8
8
  @@active_connections = {}
9
- @@allow_concurrency = false
9
+ @@allow_concurrency = true
10
10
 
11
11
  def thread_safe_active_connections
12
12
  @@active_connections[Thread.current.object_id] ||= {}
@@ -165,11 +165,11 @@ module ActiveLdap
165
165
  connection.schema
166
166
  end
167
167
 
168
- private
169
168
  def active_connection_key(k=self)
170
169
  k.name.blank? ? k.object_id : k.name
171
170
  end
172
171
 
172
+ private
173
173
  def determine_active_connection_name
174
174
  key = active_connection_key
175
175
  if active_connections[key] or configuration(key)
@@ -17,11 +17,12 @@ module ActiveLdap
17
17
  if attribute_or_name.is_a?(Schema::Attribute)
18
18
  name = attribute_or_name.name
19
19
  else
20
- attribute = schema.attribute(attribute_or_name.to_s)
20
+ attribute_name = attribute_or_name.to_s
21
+ attribute = schema.attribute(attribute_name)
21
22
  return nil if attribute.id.nil?
22
- if attribute.name == attribute_or_name or
23
- attribute.aliases.include?(attribute_or_name.to_s)
24
- name = attribute_or_name
23
+ if attribute.name == attribute_name or
24
+ attribute.aliases.include?(attribute_name)
25
+ name = attribute_name
25
26
  else
26
27
  return nil
27
28
  end
@@ -22,9 +22,23 @@ module ActiveLdap
22
22
  end
23
23
 
24
24
  module Common
25
- VALID_SEARCH_OPTIONS = [:attribute, :value, :filter, :prefix,
26
- :classes, :scope, :limit, :attributes,
27
- :sort_by, :order, :connection, :base, :offset]
25
+ VALID_SEARCH_OPTIONS = [
26
+ :attribute,
27
+ :value,
28
+ :filter,
29
+ :prefix,
30
+ :classes,
31
+ :scope,
32
+ :limit,
33
+ :attributes,
34
+ :sort_by,
35
+ :order,
36
+ :connection,
37
+ :base,
38
+ :offset,
39
+ :use_paged_results,
40
+ :page_size,
41
+ ]
28
42
 
29
43
  def search(options={}, &block)
30
44
  validate_search_options(options)
@@ -62,6 +76,8 @@ module ActiveLdap
62
76
  :attributes => requested_attributes,
63
77
  :sort_by => options[:sort_by] || sort_by,
64
78
  :order => options[:order] || order,
79
+ :use_paged_results => options[:use_paged_results],
80
+ :page_size => options[:page_size],
65
81
  }
66
82
  options[:connection] ||= connection
67
83
  values = []
@@ -96,10 +112,11 @@ module ActiveLdap
96
112
  }
97
113
 
98
114
  attribute = attr || ensure_search_attribute
115
+ escaped_value = DN.escape_value(value)
99
116
  options_for_non_leaf = {
100
117
  :attribute => attr,
101
118
  :value => value,
102
- :prefix => ["#{attribute}=#{value}", prefix].compact.join(","),
119
+ :prefix => ["#{attribute}=#{escaped_value}", prefix].compact.join(","),
103
120
  :limit => 1,
104
121
  :scope => :base,
105
122
  }
@@ -81,9 +81,10 @@ module ActiveLdap
81
81
  end
82
82
  end
83
83
 
84
- def reload
84
+ def reload(options={})
85
85
  clear_association_cache
86
- _, attributes = search(:value => id).find do |_dn, _attributes|
86
+ search_options = options.merge(value: id)
87
+ _, attributes = search(search_options).find do |_dn, _attributes|
87
88
  dn == _dn
88
89
  end
89
90
  if attributes.nil?
@@ -53,15 +53,23 @@ module ActiveLdap
53
53
  errors.empty? && output
54
54
  end
55
55
 
56
- def save(*)
57
- valid? ? super : false
56
+ def save(**options)
57
+ perform_validations(options) ? super : false
58
58
  end
59
59
 
60
- def save!(*)
61
- valid? ? super : raise(EntryInvalid.new(self))
60
+ def save!(**options)
61
+ perform_validations(options) ? super : raise(EntryInvalid.new(self))
62
62
  end
63
63
 
64
64
  private
65
+ def perform_validations(options)
66
+ if options[:validate] == false
67
+ true
68
+ else
69
+ valid?(options[:context])
70
+ end
71
+ end
72
+
65
73
  def format_validation_message(format, parameters)
66
74
  format % parameters
67
75
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveLdap
2
- VERSION = "5.2.4"
2
+ VERSION = "6.1.0"
3
3
  end