ldaptic 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2007 Tim Pope
1
+ Copyright (c) Tim Pope
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -29,18 +29,15 @@ a dynamically created module into a namespace of your choosing.
29
29
  )
30
30
  end
31
31
 
32
- The adapter field can usually be omitted as it defaults to :ldap_conn or
33
- :net_ldap, based on which of the above two gems can be found (though you might
34
- want to use the :active_directory adapter, which depends on ruby-ldap,
35
- instead). If the base is omitted, it will use the first naming context on the
36
- server (usually what you want).
32
+ The adapter can usually be omitted as it defaults to :ldap_conn or :net_ldap,
33
+ based on which of the above two gems can be found. If the base is omitted, it
34
+ will use the first naming context on the server (usually what you want).
37
35
 
38
36
  Entries are retrieved using the search method. Named parameters include
39
- :base, :filter, :sort, :limit, :scope, and :attributes. All are optional.
37
+ :base, :scope, :filter, :attributes, :scope, and :limit. All are optional.
40
38
 
41
39
  entries = Example.search(
42
- :filter => {:objectClass => 'inetOrgPerson'}
43
- :sort => :cn,
40
+ :filter => {:objectClass => 'inetOrgPerson'},
44
41
  :limit => 10
45
42
  )
46
43
 
@@ -61,7 +58,7 @@ Predictably, entries have attribute readers and writers.
61
58
  => <["root"]>
62
59
  >> entry[:cn] = "admin"
63
60
 
64
- The returned object is an attribute set and behaves similar to an array. Some
61
+ The returned object is an attribute set and is similar to an array. Some
65
62
  attributes are marked by the server as "single value;" those will return the
66
63
  first element on method access but an attribute set on indexing access, for
67
64
  programmatic convenience.
@@ -80,7 +77,7 @@ The indexing syntax can also be used to create and fetch children.
80
77
  >> users[:cn=>'admin']
81
78
  => #<Example::InetOrgPerson cn=admin,ou=Users,dc=example,dc=com ...>
82
79
 
83
- Entries also implement many of the standard methods you've come to expect in
80
+ Entry also implements many of the standard methods you've come to expect in
84
81
  an Active Record world (save, valid?, errors, to_param, attributes, ...).
85
82
  In fact, it is fully Active Model compliant.
86
83
 
@@ -89,10 +86,6 @@ methods like search), Ldaptic::Entry, and Ldaptic::AttributeSet.
89
86
 
90
87
  == To Do
91
88
 
92
- * Verify everything still works. The tests take care of much of this, but
93
- there are no integration tests for the adapters. In particular, I have no
94
- way to verify the Active Directory adapter still works.
95
-
96
89
  * The test suite (reflecting my fledgling testing abilities from 2007) is more
97
90
  smoke test than BDD. Perhaps switch to RSpec in the quest to rectify this.
98
91
 
@@ -100,5 +93,3 @@ methods like search), Ldaptic::Entry, and Ldaptic::AttributeSet.
100
93
  Record") are in the GitHub issue tracker. Vote for and comment on the ones
101
94
  you would find useful, as most are on hold until someone has a real use
102
95
  case.
103
-
104
- * Pick a better name? Ldaptic has an ambiguous spelling.
@@ -3,6 +3,11 @@ require 'ldaptic/adapters/active_directory_ext'
3
3
 
4
4
  module Ldaptic
5
5
  module Adapters
6
+ # Before using this adapter, try the :ldap_conn adapter. The notes below
7
+ # were originally thought to apply to all Active Directory servers, but now
8
+ # I suspect they are peculiarities of a former employer's setup. This
9
+ # adapter is a candidate for removal.
10
+ #
6
11
  # ActiveDirectoryAdapter is a LDAPConnAdapter with some Active Directory
7
12
  # specific behaviors. To help mitigate server timeout issues, this adapter
8
13
  # binds on each request and unbinds afterwards. For search requests, the
@@ -46,11 +51,10 @@ module Ldaptic
46
51
  username = [@options[:domain], username].join("\\")
47
52
  else
48
53
  conn = new_connection(3268)
49
- dn = conn.search2("", 0, "(objectClass=*", ['defaultNamingContext']).first['defaultNamingContext']
54
+ dn = conn.search2("", 0, "(objectClass=*)", ['defaultNamingContext']).first['defaultNamingContext']
50
55
  if dn
51
- domain = Ldaptic::DN(dn).rdns.map {|rdn| rdn[:dc]}.compact
52
- unless domain.empty?
53
- username = [username, domain.join(".")].join("@")
56
+ if domain = Ldaptic::DN(dn).domain
57
+ username = [username, domain].join('@')
54
58
  end
55
59
  end
56
60
  end
@@ -70,7 +74,7 @@ module Ldaptic
70
74
  end
71
75
 
72
76
  def with_writer(&block)
73
- with_port(389, &block)
77
+ with_port(@options[:port] || 389, &block)
74
78
  end
75
79
 
76
80
  end
@@ -1,3 +1,5 @@
1
+ require 'date'
2
+
1
3
  # Converts an integer representing the number of microseconds since January 1,
2
4
  # 1600 to a DateTime.
3
5
  def DateTime.microsoft(tinies)
@@ -153,10 +153,6 @@ module Ldaptic
153
153
  end
154
154
 
155
155
  def search_parameters(options = {})
156
- case options[:sort]
157
- when Proc, Method then s_attr, s_proc = nil, options[:sort]
158
- else s_attr, s_proc = options[:sort], nil
159
- end
160
156
  [
161
157
  options[:base],
162
158
  options[:scope],
@@ -165,8 +161,6 @@ module Ldaptic
165
161
  options[:attributes_only],
166
162
  options[:timeout].to_i,
167
163
  ((options[:timeout].to_f % 1) * 1e6).round,
168
- s_attr.to_s,
169
- s_proc
170
164
  ]
171
165
  end
172
166
 
@@ -31,9 +31,9 @@ module Ldaptic
31
31
  @syntax = @entry.namespace.attribute_syntax(@name)
32
32
  @target = target
33
33
  if @type.nil?
34
- @entry.logger "Unknown type for attribute #@name"
34
+ @entry.logger.warn "Unknown type for attribute #@name"
35
35
  elsif @syntax.nil?
36
- @entry.logger "Unknown syntax #{@type.syntax_oid} for attribute #{@name}"
36
+ @entry.logger.warn "Unknown syntax #{@type.syntax_oid} for attribute #{@name}"
37
37
  end
38
38
  end
39
39
 
@@ -77,15 +77,37 @@ module Ldaptic
77
77
  @target.empty?
78
78
  end
79
79
 
80
- # Adds the given attributes, discarding duplicates. Currently, a duplicate
81
- # is determined by == (case sensitive) rather than by the server (typically
82
- # case insensitive). All arrays are flattened.
83
- def add(*attributes)
84
- dest = @target.dup
85
- safe_array(attributes).each do |attribute|
86
- dest.push(attribute) unless include?(attribute)
80
+ def index(*args, &block)
81
+ if block_given? || args.size != 1
82
+ return to_a.index(*args, &block)
83
+ else
84
+ target = matchable(args.first)
85
+ @target.each_with_index do |candidate, index|
86
+ return index if matchable(candidate) == target
87
+ end
87
88
  end
88
- replace(dest)
89
+ nil
90
+ end
91
+ alias find_index index
92
+ alias rindex index
93
+
94
+ def include?(target)
95
+ !!index(target)
96
+ end
97
+
98
+ def exclude?(target)
99
+ !index(target)
100
+ end
101
+
102
+ # Like #include?, but asks the server rather than checking locally.
103
+ def compare(target)
104
+ @entry.compare(@name, target)
105
+ end
106
+
107
+ # Adds the given attributes, discarding duplicates. All arrays are
108
+ # flattened.
109
+ def add(*attributes)
110
+ replace(@target + safe_array(attributes))
89
111
  end
90
112
  alias << add
91
113
  alias concat add
@@ -102,7 +124,16 @@ module Ldaptic
102
124
  def replace(*attributes)
103
125
  attributes = safe_array(attributes)
104
126
  user_modification_guard
105
- @target.replace(attributes)
127
+ seen = {}
128
+ filtered = []
129
+ attributes.each do |value|
130
+ matchable = matchable(value)
131
+ unless seen[matchable]
132
+ filtered << value
133
+ seen[matchable] = true
134
+ end
135
+ end
136
+ @target.replace(filtered)
106
137
  self
107
138
  end
108
139
 
@@ -119,16 +150,13 @@ module Ldaptic
119
150
 
120
151
  # Remove the given attributes given, functioning more or less like
121
152
  # Array#delete, except accepting multiple arguments.
122
- #
123
- # Two passes are made to find each element, one case sensitive and one
124
- # ignoring case, before giving up.
125
153
  def delete(*attributes, &block)
126
154
  return clear if attributes.flatten.empty?
127
155
  dest = @target.dup
128
156
  ret = []
129
157
  safe_array(attributes).each do |attribute|
130
158
  ret << dest.delete(attribute) do
131
- match = dest.detect {|x| x.downcase == attribute.downcase}
159
+ match = dest.detect {|x| matchable(x) == matchable(attribute)}
132
160
  if match
133
161
  dest.delete(match)
134
162
  else
@@ -158,8 +186,7 @@ module Ldaptic
158
186
  alias map! collect!
159
187
 
160
188
  def insert(index, *objects)
161
- user_modification_guard
162
- @target.insert(index, *safe_array(objects))
189
+ replace(@target.dup.insert(index, *safe_array(objects)))
163
190
  self
164
191
  end
165
192
 
@@ -261,6 +288,14 @@ module Ldaptic
261
288
  end
262
289
  end
263
290
 
291
+ def matchable(value)
292
+ if @type
293
+ @type.matchable(value)
294
+ else
295
+ format(value)
296
+ end
297
+ end
298
+
264
299
  def safe_array(attributes)
265
300
  Array(attributes).flatten.compact.map {|x| format(x)}
266
301
  end
data/lib/ldaptic/dn.rb CHANGED
@@ -75,13 +75,13 @@ module Ldaptic
75
75
  filter = "(objectClass=*)"
76
76
  if source.respond_to?(:search2_ext)
77
77
  source.search2(
78
- self.to_s,
78
+ to_s,
79
79
  scope,
80
80
  filter
81
81
  )
82
82
  elsif source.respond_to?(:search)
83
83
  Array(source.search(
84
- :base => self.to_s,
84
+ :base => to_s,
85
85
  :scope => scope,
86
86
  :filter => filter,
87
87
  :limit => 1
@@ -113,6 +113,12 @@ module Ldaptic
113
113
  end
114
114
  end
115
115
 
116
+ # Join all DC elements with periods.
117
+ def domain
118
+ components = rdns.map {|rdn| rdn[:dc]}.compact
119
+ components.join('.') unless components.empty?
120
+ end
121
+
116
122
  def parent
117
123
  Ldaptic::DN(rdns[1..-1], source)
118
124
  end
@@ -142,12 +148,10 @@ module Ldaptic
142
148
  end
143
149
  end
144
150
  if other.kind_of?(Ldaptic::DN)
145
- self.rdns == other.rdns
151
+ rdns == other.rdns
146
152
  else
147
153
  super
148
154
  end
149
- # rescue
150
- # super
151
155
  end
152
156
 
153
157
  # Pass in one or more hashes to augment the DN. Otherwise, this behaves
@@ -338,7 +342,7 @@ module Ldaptic
338
342
  end
339
343
 
340
344
  def merge(hash)
341
- self.dup.update(hash)
345
+ dup.update(hash)
342
346
  end
343
347
 
344
348
  def delete(key)
data/lib/ldaptic/entry.rb CHANGED
@@ -48,11 +48,18 @@ module Ldaptic
48
48
  def create_accessors #:nodoc:
49
49
  to_be_evaled = ""
50
50
  (may(false) + must(false)).each do |attr|
51
- method = attr.to_s.tr_s('-_', '_-')
52
- to_be_evaled << <<-RUBY
53
- def #{method}() read_attribute('#{attr}').one end
54
- def #{method}=(value) write_attribute('#{attr}', value) end
55
- RUBY
51
+ if type = namespace.attribute_type(attr)
52
+ names = type.names
53
+ else
54
+ names = [attr]
55
+ end
56
+ names.each do |name|
57
+ method = name.to_s.tr_s('-_', '_-')
58
+ to_be_evaled << <<-RUBY
59
+ def #{method}() read_attribute('#{attr}').one end
60
+ def #{method}=(value) write_attribute('#{attr}', value) end
61
+ RUBY
62
+ end
56
63
  end
57
64
  class_eval(to_be_evaled, __FILE__, __LINE__)
58
65
  end
@@ -176,7 +183,7 @@ module Ldaptic
176
183
  @attributes = {}
177
184
  data = data.dup
178
185
  if dn = data.delete('dn') || data.delete(:dn)
179
- dn.first if dn.kind_of?(Array)
186
+ dn = dn.first if dn.kind_of?(Array)
180
187
  self.dn = dn
181
188
  end
182
189
  merge_attributes(data)
@@ -273,7 +280,11 @@ module Ldaptic
273
280
  # #method_missing delegates to this method, method names with underscores
274
281
  # map to attributes with hyphens.
275
282
  def read_attribute(key)
276
- key = Ldaptic.encode(key)
283
+ if type = namespace.attribute_type(key)
284
+ key = type.names.first
285
+ else
286
+ key = Ldaptic.encode(key)
287
+ end
277
288
  @attributes[key] ||= ((@original_attributes || {}).fetch(key, [])).dup
278
289
  Ldaptic::AttributeSet.new(self, key, @attributes[key])
279
290
  end
@@ -302,7 +313,7 @@ module Ldaptic
302
313
  # Changes are not committed to the server until #save is called.
303
314
  def write_attribute(key, values)
304
315
  set = read_attribute(key)
305
- if values.respond_to?(:to_str) && set.syntax_object && set.syntax_object.error("1\n1")
316
+ if values.respond_to?(:to_str) && !set.single_value? && set.syntax_object && set.syntax_object.error("1\n1")
306
317
  values = values.split(/\r?\n/)
307
318
  elsif values == ''
308
319
  values = []
@@ -357,7 +368,6 @@ module Ldaptic
357
368
  namespace.compare(dn, key, value)
358
369
  end
359
370
 
360
- # attr_reader :attributes
361
371
  def attribute_names
362
372
  attributes.keys
363
373
  end
@@ -370,25 +380,41 @@ module Ldaptic
370
380
  self['objectClass'].map {|c| namespace.object_class(c)} - self.class.ldap_ancestors
371
381
  end
372
382
 
373
- def must(all = true)
374
- self.class.must(all) + aux.map {|a|a.must(false)}.flatten
383
+ def must(include_aliases = false)
384
+ attrs = (self.class.must + aux.map {|a| a.must(false)}.flatten).uniq
385
+ if include_aliases
386
+ attrs.map do |attr|
387
+ type = namespace.attribute_type(attr)
388
+ type ? type.names : attr
389
+ end.flatten
390
+ else
391
+ attrs
392
+ end
375
393
  end
376
394
 
377
- def may(all = true)
378
- self.class.may(all) + aux.map {|a|a.may(false)}.flatten
395
+ def may(include_aliases = false)
396
+ attrs = (self.class.may + aux.map {|a| a.may(false)}.flatten).uniq - must
397
+ if include_aliases
398
+ attrs.map do |attr|
399
+ type = namespace.attribute_type(attr)
400
+ type ? type.names : attr
401
+ end.flatten
402
+ else
403
+ attrs
404
+ end
379
405
  end
380
406
 
381
407
  def may_must(attribute)
382
408
  attribute = Ldaptic.encode(attribute)
383
- if must.include?(attribute)
409
+ if must(true).include?(attribute)
384
410
  :must
385
- elsif may.include?(attribute)
411
+ elsif may(true).include?(attribute)
386
412
  :may
387
413
  end
388
414
  end
389
415
 
390
416
  def respond_to?(method, *) #:nodoc:
391
- both = may + must
417
+ both = may(true) + must(true)
392
418
  super || (both + both.map {|x| "#{x}="} + both.map {|x| "#{x}-before-type-cast"}).include?(Ldaptic.encode(method.to_sym))
393
419
  end
394
420
 
@@ -435,7 +461,10 @@ module Ldaptic
435
461
  alias find /
436
462
 
437
463
  def fetch(dn = self.dn, options = {}) #:nodoc:
438
- search({:base => dn}.merge(options))
464
+ if dn.kind_of?(Hash)
465
+ dn = self.dn/dn
466
+ end
467
+ namespace.fetch(dn, options)
439
468
  end
440
469
 
441
470
  # If a Hash or a String containing "=" is given, the argument is treated as
@@ -144,7 +144,7 @@ module Ldaptic
144
144
  def process
145
145
  "(#{@array*''})" if @array.compact.size > 1
146
146
  end
147
- def to_net_ldap_filter #:nodoc
147
+ def to_net_ldap_filter #:nodoc:
148
148
  @array[1..-1].inject {|m, o| m.to_net_ldap_filter.send(@array.first, o.to_net_ldap_filter)}
149
149
  end
150
150
  end
@@ -0,0 +1,80 @@
1
+ require 'ldaptic/syntaxes'
2
+
3
+ module Ldaptic
4
+ # RFC4517 - Lightweight Directory Access Protocol (LDAP): Syntaxes and Matching Rules
5
+ # RFC4518 - Lightweight Directory Access Protocol (LDAP): Internationalized
6
+ # String Preparation
7
+ module MatchingRules
8
+ def self.for(name)
9
+ name = name.to_s
10
+ name = name[0..0].upcase + name[1..-1].to_s
11
+ if !name.empty? && const_defined?(name)
12
+ const_get(name)
13
+ else
14
+ CaseIgnoreMatch
15
+ end
16
+ end
17
+
18
+ class OctetStringMatch
19
+ def matchable(value)
20
+ value
21
+ end
22
+
23
+ def match(one, two)
24
+ matchable(one) == matchable(two)
25
+ end
26
+ end
27
+
28
+ class Boolean < OctetStringMatch
29
+ end
30
+
31
+ class CaseExactMatch < OctetStringMatch
32
+ def matchable(value)
33
+ super.gsub(/ +/, ' ').sub(/\A */, ' ').sub(/ *\z/, ' ')
34
+ end
35
+ end
36
+
37
+ class CaseExactIA5Match < CaseExactMatch
38
+ end
39
+
40
+ class CaseIgnoreMatch < CaseExactMatch
41
+ def matchable(value)
42
+ super.downcase
43
+ end
44
+ end
45
+
46
+ class CaseIgnoreIA5Match < CaseIgnoreMatch
47
+ end
48
+
49
+ class CaseIgnoreListMatch < CaseIgnoreMatch
50
+ end
51
+
52
+ class GeneralizedTimeMatch < OctetStringMatch
53
+ def matchable(value)
54
+ Ldaptic::Syntaxes::GeneralizedTime.parse(value)
55
+ end
56
+ end
57
+
58
+ class NumericStringMatch < OctetStringMatch
59
+ def matchable(value)
60
+ super.delete(' ')
61
+ end
62
+ end
63
+
64
+ class DistinguishedNameMatch < OctetStringMatch
65
+ def matchable(value)
66
+ Ldaptic::DN(value)
67
+ end
68
+ end
69
+
70
+ class TelephoneNumberMatch < CaseIgnoreMatch
71
+ # Doesn't remove unicode hyphen equivalents \u058A, \u2010, \u2011,
72
+ # \u2212, \ufe63, or \uff0d on account of unicode being so darn difficult
73
+ # to get right in both 1.8 and 1.9.
74
+ def matchable(value)
75
+ super.delete(' ').delete('-')
76
+ end
77
+ end
78
+
79
+ end
80
+ end