ruby-activeldap 0.8.1 → 0.8.2

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.
Files changed (65) hide show
  1. data/CHANGES +5 -0
  2. data/Manifest.txt +91 -25
  3. data/README +22 -0
  4. data/Rakefile +41 -8
  5. data/TODO +1 -6
  6. data/examples/config.yaml.example +5 -0
  7. data/examples/example.der +0 -0
  8. data/examples/example.jpg +0 -0
  9. data/examples/groupadd +41 -0
  10. data/examples/groupdel +35 -0
  11. data/examples/groupls +49 -0
  12. data/examples/groupmod +42 -0
  13. data/examples/lpasswd +55 -0
  14. data/examples/objects/group.rb +13 -0
  15. data/examples/objects/ou.rb +4 -0
  16. data/examples/objects/user.rb +20 -0
  17. data/examples/ouadd +38 -0
  18. data/examples/useradd +45 -0
  19. data/examples/useradd-binary +50 -0
  20. data/examples/userdel +34 -0
  21. data/examples/userls +50 -0
  22. data/examples/usermod +42 -0
  23. data/examples/usermod-binary-add +47 -0
  24. data/examples/usermod-binary-add-time +51 -0
  25. data/examples/usermod-binary-del +48 -0
  26. data/examples/usermod-lang-add +43 -0
  27. data/lib/active_ldap.rb +213 -214
  28. data/lib/active_ldap/adapter/base.rb +461 -0
  29. data/lib/active_ldap/adapter/ldap.rb +232 -0
  30. data/lib/active_ldap/adapter/ldap_ext.rb +69 -0
  31. data/lib/active_ldap/adapter/net_ldap.rb +288 -0
  32. data/lib/active_ldap/adapter/net_ldap_ext.rb +29 -0
  33. data/lib/active_ldap/association/belongs_to.rb +3 -1
  34. data/lib/active_ldap/association/belongs_to_many.rb +5 -6
  35. data/lib/active_ldap/association/has_many.rb +9 -17
  36. data/lib/active_ldap/association/has_many_wrap.rb +4 -5
  37. data/lib/active_ldap/attributes.rb +4 -0
  38. data/lib/active_ldap/base.rb +201 -56
  39. data/lib/active_ldap/configuration.rb +11 -1
  40. data/lib/active_ldap/connection.rb +15 -9
  41. data/lib/active_ldap/distinguished_name.rb +246 -0
  42. data/lib/active_ldap/ldap_error.rb +74 -0
  43. data/lib/active_ldap/object_class.rb +9 -5
  44. data/lib/active_ldap/schema.rb +50 -9
  45. data/lib/active_ldap/validations.rb +11 -13
  46. data/rails/plugin/active_ldap/generators/scaffold_al/scaffold_al_generator.rb +7 -0
  47. data/rails/plugin/active_ldap/generators/scaffold_al/templates/ldap.yml +21 -0
  48. data/rails/plugin/active_ldap/init.rb +10 -4
  49. data/test/al-test-utils.rb +46 -3
  50. data/test/run-test.rb +16 -4
  51. data/test/test-unit-ext/always-show-result.rb +28 -0
  52. data/test/test-unit-ext/priority.rb +163 -0
  53. data/test/test_adapter.rb +81 -0
  54. data/test/test_attributes.rb +8 -1
  55. data/test/test_base.rb +132 -3
  56. data/test/test_base_per_instance.rb +14 -3
  57. data/test/test_connection.rb +19 -0
  58. data/test/test_dn.rb +161 -0
  59. data/test/test_find.rb +24 -0
  60. data/test/test_object_class.rb +15 -2
  61. data/test/test_schema.rb +108 -1
  62. metadata +111 -41
  63. data/lib/active_ldap/adaptor/base.rb +0 -29
  64. data/lib/active_ldap/adaptor/ldap.rb +0 -466
  65. data/lib/active_ldap/ldap.rb +0 -113
@@ -4,6 +4,21 @@ class TestObjectClass < Test::Unit::TestCase
4
4
  include AlTestUtils
5
5
 
6
6
  priority :must
7
+ def test_ensure_recommended_classes
8
+ make_temporary_group do |group|
9
+ added_class = "labeledURIObject"
10
+
11
+ assert_equal([], group.class.recommended_classes)
12
+ group.class.recommended_classes += [added_class]
13
+ assert_equal([added_class], group.class.recommended_classes)
14
+
15
+ assert_equal([added_class],
16
+ group.class.recommended_classes - group.classes)
17
+ group.ensure_recommended_classes
18
+ assert_equal([],
19
+ group.class.recommended_classes - group.classes)
20
+ end
21
+ end
7
22
 
8
23
  priority :normal
9
24
  def test_unknown_object_class
@@ -27,6 +42,4 @@ class TestObjectClass < Test::Unit::TestCase
27
42
  assert_raises(TypeError) {group.add_class(:posixAccount)}
28
43
  end
29
44
  end
30
-
31
- priority :normal
32
45
  end
data/test/test_schema.rb CHANGED
@@ -1,6 +1,104 @@
1
1
  require 'al-test-utils'
2
2
 
3
3
  class TestSchema < Test::Unit::TestCase
4
+ priority :must
5
+ def test_empty_schema
6
+ assert_make_schema_with_empty_entries(nil)
7
+ assert_make_schema_with_empty_entries({})
8
+ assert_make_schema_with_empty_entries({"someValues" => ["aValue"]})
9
+ end
10
+
11
+ def test_empty_schema_value
12
+ schema = ActiveLdap::Schema.new({"attributeTypes" => nil})
13
+ assert_equal([], schema["attributeTypes", "cn", "DESC"])
14
+ end
15
+
16
+ priority :normal
17
+ def test_attribute_name_with_under_score
18
+ top_schema =
19
+ "( 2.5.6.0 NAME 'Top' STRUCTURAL MUST objectClass MAY ( " +
20
+ "cAPublicKey $ cAPrivateKey $ certificateValidityInterval $ " +
21
+ "authorityRevocation $ lastReferencedTime $ " +
22
+ "equivalentToMe $ ACL $ backLink $ binderyProperty $ " +
23
+ "Obituary $ Reference $ revision $ " +
24
+ "ndsCrossCertificatePair $ certificateRevocation $ " +
25
+ "usedBy $ GUID $ otherGUID $ DirXML-Associations $ " +
26
+ "creatorsName $ modifiersName $ unknownBaseClass $ " +
27
+ "unknownAuxiliaryClass $ auditFileLink $ " +
28
+ "masvProposedLabelel $ masvDefaultRange $ " +
29
+ "masvAuthorizedRange $ objectVersion $ " +
30
+ "auxClassCompatibility $ rbsAssignedRoles $ " +
31
+ "rbsOwnedCollections $ rbsAssignedRoles2 $ " +
32
+ "rbsOwnedCollections2 ) X-NDS_NONREMOVABLE '1' " +
33
+ "X-NDS_ACL_TEMPLATES '16#subtree#[Creator]#[Entry Rights]' )"
34
+
35
+ expect = {
36
+ :name => ["Top"],
37
+ :structural => ["TRUE"],
38
+ :must => ["objectClass"],
39
+ :x_nds_nonremovable => ["1"],
40
+ :x_nds_acl_templates => ['16#subtree#[Creator]#[Entry Rights]'],
41
+ }
42
+ assert_schema(expect, "Top", top_schema)
43
+ end
44
+
45
+ def test_sup_with_oid_start_with_upper_case
46
+ organizational_person_schema =
47
+ "( 2.5.6.7 NAME 'organizationalPerson' SUP Person STRUCTURAL MAY " +
48
+ "( facsimileTelephoneNumber $ l $ eMailAddress $ ou $ " +
49
+ "physicalDeliveryOfficeName $ postalAddress $ postalCode $ " +
50
+ "postOfficeBox $ st $ street $ title $ mailboxLocation $ " +
51
+ "mailboxID $ uid $ mail $ employeeNumber $ destinationIndicator $ " +
52
+ "internationaliSDNNumber $ preferredDeliveryMethod $ " +
53
+ "registeredAddress $ teletexTerminalIdentifier $ telexNumber $ " +
54
+ "x121Address $ businessCategory $ roomNumber $ x500UniqueIdentifier " +
55
+ ") X-NDS_NAMING ( 'cn' 'ou' 'uid' ) X-NDS_CONTAINMENT ( " +
56
+ "'Organization' 'organizationalUnit' 'domain' ) X-NDS_NAME " +
57
+ "'Organizational Person' X-NDS_NOT_CONTAINER '1' " +
58
+ "X-NDS_NONREMOVABLE '1' )"
59
+
60
+ expect = {
61
+ :name => ["organizationalPerson"],
62
+ :sup => ["Person"],
63
+ :structural => ["TRUE"],
64
+ :x_nds_naming => ["cn", "ou", "uid"],
65
+ :x_nds_containment => ["Organization", "organizationalUnit", "domain"],
66
+ :x_nds_name => ["Organizational Person"],
67
+ :x_nds_not_container => ["1"],
68
+ :x_nds_nonremovable => ["1"],
69
+ }
70
+ assert_schema(expect, "organizationalPerson",
71
+ organizational_person_schema)
72
+ end
73
+
74
+ def test_text_oid
75
+ text_oid_schema = "( mysite-oid NAME 'mysite' " +
76
+ "SUP groupofuniquenames STRUCTURAL " +
77
+ "MUST ( mysitelang $ mysiteurl ) " +
78
+ "MAY ( mysitealias $ mysitecmsurl ) " +
79
+ "X-ORIGIN 'user defined' )"
80
+ expect = {
81
+ :name => ["mysite"],
82
+ :sup => ["groupofuniquenames"],
83
+ :structural => ["TRUE"],
84
+ :must => %w(mysitelang mysiteurl),
85
+ :may => %w(mysitealias mysitecmsurl),
86
+ :x_origin => ["user defined"]
87
+ }
88
+ assert_schema(expect, "mysite", text_oid_schema)
89
+
90
+ text_oid_attribute_schema = "( mysiteurl-oid NAME 'mysiteurl' " +
91
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 " +
92
+ "SINGLE-VALUE X-ORIGIN 'user defined' )"
93
+ expect = {
94
+ :name => ["mysiteurl"],
95
+ :syntax => ["1.3.6.1.4.1.1466.115.121.1.15"],
96
+ :single_value => ["TRUE"],
97
+ :x_origin => ["user defined"]
98
+ }
99
+ assert_schema(expect, "mysiteurl", text_oid_attribute_schema)
100
+ end
101
+
4
102
  def test_name_as_key
5
103
  top_schema = "( 2.5.6.0 NAME 'top' DESC 'top of the superclass chain' " +
6
104
  "ABSTRACT MUST objectClass )"
@@ -159,8 +257,17 @@ class TestSchema < Test::Unit::TestCase
159
257
  expect.each do |key, value|
160
258
  normalized_key = key.to_s.gsub(/_/, "-")
161
259
  normalized_expect[normalized_key] = value
162
- actual[normalized_key] = schema.attr(sub, name, normalized_key)
260
+ actual[normalized_key] = schema[sub, name, normalized_key]
163
261
  end
164
262
  assert_equal(normalized_expect, actual)
165
263
  end
264
+
265
+ def assert_make_schema_with_empty_entries(entries)
266
+ schema = ActiveLdap::Schema.new(entries)
267
+ assert_equal([], schema["attributeTypes", "cn", "DESC"])
268
+ assert_equal([], schema["ldapSyntaxes",
269
+ "1.3.6.1.4.1.1466.115.121.1.5",
270
+ "DESC"])
271
+ assert_equal([], schema["objectClasses", "posixAccount", "MUST"])
272
+ end
166
273
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: ruby-activeldap
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.8.1
7
- date: 2007-01-28 00:00:00 +00:00
6
+ version: 0.8.2
7
+ date: 2007-05-15 00:00:00 -07:00
8
8
  summary: Ruby/ActiveLdap is a object-oriented API to LDAP
9
9
  require_paths:
10
10
  - lib
@@ -32,72 +32,133 @@ authors:
32
32
  - Will Drewry
33
33
  - Kouhei Sutou
34
34
  files:
35
- - Rakefile
36
- - LICENSE
37
- - TODO
38
- - COPYING
39
35
  - CHANGES
40
- - README
36
+ - COPYING
37
+ - LICENSE
41
38
  - Manifest.txt
42
- - lib/active_ldap/adaptor/ldap.rb
43
- - lib/active_ldap/adaptor/base.rb
44
- - lib/active_ldap/association/has_many_wrap.rb
45
- - lib/active_ldap/association/has_many.rb
46
- - lib/active_ldap/association/proxy.rb
39
+ - README
40
+ - Rakefile
41
+ - TODO
42
+ - benchmark/bench-al.rb
43
+ - examples/config.yaml.example
44
+ - examples/example.der
45
+ - examples/example.jpg
46
+ - examples/groupadd
47
+ - examples/groupdel
48
+ - examples/groupls
49
+ - examples/groupmod
50
+ - examples/lpasswd
51
+ - examples/objects/group.rb
52
+ - examples/objects/ou.rb
53
+ - examples/objects/user.rb
54
+ - examples/ouadd
55
+ - examples/useradd
56
+ - examples/useradd-binary
57
+ - examples/userdel
58
+ - examples/userls
59
+ - examples/usermod
60
+ - examples/usermod-binary-add
61
+ - examples/usermod-binary-add-time
62
+ - examples/usermod-binary-del
63
+ - examples/usermod-lang-add
64
+ - lib/active_ldap.rb
65
+ - lib/active_ldap/adapter/base.rb
66
+ - lib/active_ldap/adapter/ldap.rb
67
+ - lib/active_ldap/adapter/ldap_ext.rb
68
+ - lib/active_ldap/adapter/net_ldap.rb
69
+ - lib/active_ldap/adapter/net_ldap_ext.rb
70
+ - lib/active_ldap/association/belongs_to.rb
47
71
  - lib/active_ldap/association/belongs_to_many.rb
48
72
  - lib/active_ldap/association/collection.rb
49
- - lib/active_ldap/association/belongs_to.rb
50
- - lib/active_ldap/validations.rb
51
- - lib/active_ldap/command.rb
52
- - lib/active_ldap/callbacks.rb
53
- - lib/active_ldap/ldap.rb
54
- - lib/active_ldap/timeout_stub.rb
55
- - lib/active_ldap/timeout.rb
73
+ - lib/active_ldap/association/has_many.rb
74
+ - lib/active_ldap/association/has_many_wrap.rb
75
+ - lib/active_ldap/association/proxy.rb
76
+ - lib/active_ldap/associations.rb
56
77
  - lib/active_ldap/attributes.rb
57
- - lib/active_ldap/object_class.rb
78
+ - lib/active_ldap/base.rb
79
+ - lib/active_ldap/callbacks.rb
80
+ - lib/active_ldap/command.rb
81
+ - lib/active_ldap/configuration.rb
58
82
  - lib/active_ldap/connection.rb
59
- - lib/active_ldap/associations.rb
60
- - lib/active_ldap/user_password.rb
83
+ - lib/active_ldap/distinguished_name.rb
84
+ - lib/active_ldap/ldap_error.rb
85
+ - lib/active_ldap/object_class.rb
61
86
  - lib/active_ldap/schema.rb
62
- - lib/active_ldap/configuration.rb
63
- - lib/active_ldap/base.rb
64
- - lib/active_ldap.rb
65
- - benchmark/bench-al.rb
66
- - rails/plugin/active_ldap/init.rb
87
+ - lib/active_ldap/timeout.rb
88
+ - lib/active_ldap/timeout_stub.rb
89
+ - lib/active_ldap/user_password.rb
90
+ - lib/active_ldap/validations.rb
67
91
  - rails/plugin/active_ldap/README
68
- test_files:
69
- - test/test-unit-ext
70
- - test/test_useradd.rb
71
- - test/test_user.rb
92
+ - rails/plugin/active_ldap/generators/scaffold_al/scaffold_al_generator.rb
93
+ - rails/plugin/active_ldap/generators/scaffold_al/templates/ldap.yml
94
+ - rails/plugin/active_ldap/init.rb
95
+ - test/TODO
96
+ - test/al-test-utils.rb
72
97
  - test/command.rb
73
- - test/test_usermod.rb
98
+ - test/config.yaml.sample
99
+ - test/run-test.rb
100
+ - test/test-unit-ext.rb
101
+ - test/test-unit-ext/always-show-result.rb
102
+ - test/test-unit-ext/priority.rb
103
+ - test/test_adapter.rb
104
+ - test/test_associations.rb
105
+ - test/test_attributes.rb
106
+ - test/test_base.rb
107
+ - test/test_base_per_instance.rb
74
108
  - test/test_bind.rb
109
+ - test/test_callback.rb
110
+ - test/test_connection.rb
111
+ - test/test_connection_per_class.rb
112
+ - test/test_dn.rb
113
+ - test/test_find.rb
114
+ - test/test_groupadd.rb
115
+ - test/test_groupdel.rb
116
+ - test/test_groupls.rb
117
+ - test/test_groupmod.rb
118
+ - test/test_lpasswd.rb
119
+ - test/test_object_class.rb
120
+ - test/test_reflection.rb
121
+ - test/test_schema.rb
122
+ - test/test_user.rb
123
+ - test/test_user_password.rb
124
+ - test/test_useradd-binary.rb
125
+ - test/test_useradd.rb
126
+ - test/test_userdel.rb
127
+ - test/test_userls.rb
128
+ - test/test_usermod-binary-add-time.rb
129
+ - test/test_usermod-binary-add.rb
130
+ - test/test_usermod-binary-del.rb
131
+ - test/test_usermod-lang-add.rb
132
+ - test/test_usermod.rb
133
+ - test/test_validation.rb
134
+ test_files:
135
+ - test/test_user.rb
75
136
  - test/test_usermod-binary-add-time.rb
76
137
  - test/test_usermod-lang-add.rb
77
138
  - test/test_attributes.rb
78
139
  - test/test_usermod-binary-add.rb
140
+ - test/test_useradd.rb
79
141
  - test/test_validation.rb
80
- - test/test-unit-ext.rb
81
142
  - test/test_object_class.rb
82
143
  - test/test_groupmod.rb
83
144
  - test/test_associations.rb
84
145
  - test/test_user_password.rb
85
146
  - test/test_base_per_instance.rb
86
- - test/al-test-utils.rb
87
147
  - test/test_useradd-binary.rb
88
- - test/test_groupdel.rb
148
+ - test/test_bind.rb
149
+ - test/test_usermod.rb
89
150
  - test/test_find.rb
90
- - test/test_groupls.rb
91
- - test/run-test.rb
92
- - test/TODO
93
- - test/config.yaml.sample
151
+ - test/test_adapter.rb
152
+ - test/test_groupdel.rb
153
+ - test/test_connection_per_class.rb
94
154
  - test/test_reflection.rb
155
+ - test/test_groupls.rb
95
156
  - test/test_callback.rb
96
- - test/test_connection_per_class.rb
97
157
  - test/test_connection.rb
98
158
  - test/test_schema.rb
99
- - test/test_userdel.rb
159
+ - test/test_dn.rb
100
160
  - test/test_usermod-binary-del.rb
161
+ - test/test_userdel.rb
101
162
  - test/test_groupadd.rb
102
163
  - test/test_base.rb
103
164
  - test/test_userls.rb
@@ -132,3 +193,12 @@ dependencies:
132
193
  - !ruby/object:Gem::Version
133
194
  version: 0.0.0
134
195
  version:
196
+ - !ruby/object:Gem::Dependency
197
+ name: hoe
198
+ version_requirement:
199
+ version_requirements: !ruby/object:Gem::Version::Requirement
200
+ requirements:
201
+ - - ">="
202
+ - !ruby/object:Gem::Version
203
+ version: 1.2.0
204
+ version:
@@ -1,29 +0,0 @@
1
- module ActiveLdap
2
- module Adaptor
3
- class Base
4
- def initialize(config={})
5
- @connection = nil
6
- @config = config.dup
7
- @logger = @config.delete(:logger)
8
- %w(host port method timeout retry_on_timeout
9
- retry_limit retry_wait bind_dn password
10
- password_block try_sasl allow_anonymous
11
- store_password).each do |name|
12
- instance_variable_set("@#{name}", config[name.to_sym])
13
- end
14
- end
15
-
16
- private
17
- def with_timeout(try_reconnect=true, &block)
18
- begin
19
- Timeout.alarm(@timeout, &block)
20
- rescue Timeout::Error => e
21
- @logger.error {'Requested action timed out.'}
22
- retry if try_reconnect and @retry_on_timeout and reconnect
23
- @logger.error {e.message}
24
- raise TimeoutError, e.message
25
- end
26
- end
27
- end
28
- end
29
- end
@@ -1,466 +0,0 @@
1
- require 'ldap'
2
- require 'ldap/ldif'
3
- require 'ldap/schema'
4
-
5
- require 'active_ldap/ldap'
6
- require 'active_ldap/schema'
7
-
8
- require 'active_ldap/adaptor/base'
9
-
10
- class LDAP::Mod
11
- unless instance_method(:to_s).arity.zero?
12
- def to_s
13
- inspect
14
- end
15
- end
16
-
17
- alias_method :_initialize, :initialize
18
- def initialize(op, type, vals)
19
- if (LDAP::VERSION.split(/\./).collect {|x| x.to_i} <=> [0, 9, 7]) <= 0
20
- @op, @type, @vals = op, type, vals # to protect from GC
21
- end
22
- _initialize(op, type, vals)
23
- end
24
- end
25
-
26
- module ActiveLdap
27
- module Adaptor
28
- class Ldap < Base
29
- module Method
30
- class SSL
31
- def connect(host, port)
32
- LDAP::SSLConn.new(host, port, false)
33
- end
34
- end
35
-
36
- class TLS
37
- def connect(host, port)
38
- LDAP::SSLConn.new(host, port, true)
39
- end
40
- end
41
-
42
- class Plain
43
- def connect(host, port)
44
- LDAP::Conn.new(host, port)
45
- end
46
- end
47
- end
48
-
49
- SCOPE = {
50
- :base => LDAP::LDAP_SCOPE_BASE,
51
- :sub => LDAP::LDAP_SCOPE_SUBTREE,
52
- :one => LDAP::LDAP_SCOPE_ONELEVEL,
53
- }
54
-
55
- def connect(options={})
56
- method = ensure_method(options[:method] || @method)
57
- host = options[:host] || @host
58
- port = options[:port] || @port
59
-
60
- @connection = method.connect(host, port)
61
- operation(options) do
62
- @connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
63
- end
64
- bind(options)
65
- end
66
-
67
- def schema(options={})
68
- @schema ||= operation(options) do
69
- base = options[:base]
70
- attrs = options[:attrs]
71
- sec = options[:sec] || 0
72
- usec = options[:usec] || 0
73
-
74
- attrs ||= [
75
- 'objectClasses',
76
- 'attributeTypes',
77
- 'matchingRules',
78
- 'matchingRuleUse',
79
- 'dITStructureRules',
80
- 'dITContentRules',
81
- 'nameForms',
82
- 'ldapSyntaxes',
83
- ]
84
- key = 'subschemaSubentry'
85
- base ||= @connection.root_dse([key], sec, usec)[0][key][0]
86
- base ||= 'cn=schema'
87
- result = @connection.search2(base, LDAP::LDAP_SCOPE_BASE,
88
- '(objectClass=subschema)', attrs, false,
89
- sec, usec).first
90
- Schema.new(result)
91
- end
92
- # rescue
93
- # raise ConnectionError.new("Unable to retrieve schema from " +
94
- # "server (#{@method.class.downcase})")
95
- end
96
-
97
- def disconnect!(options={})
98
- return if @connection.nil?
99
- begin
100
- unbind(options)
101
- #rescue
102
- end
103
- @connection = nil
104
- # Make sure it is cleaned up
105
- # This causes Ruby/LDAP memory corruption.
106
- # GC.start
107
- end
108
-
109
- def unbind(options={})
110
- return unless bound?
111
- operation(options) do
112
- @connection.unbind
113
- end
114
- end
115
-
116
- def rebind(options={})
117
- unbind(options) if bound?
118
- connect(options)
119
- end
120
-
121
- def bind(options={})
122
- bind_dn = options[:bind_dn] || @bind_dn
123
- try_sasl = options.has_key?(:try_sasl) ? options[:try_sasl] : @try_sasl
124
- if options.has_key?(:allow_anonymous)
125
- allow_anonymous = options[:allow_anonymous]
126
- else
127
- allow_anonymous = @allow_anonymous
128
- end
129
-
130
- # Rough bind loop:
131
- # Attempt 1: SASL if available
132
- # Attempt 2: SIMPLE with credentials if password block
133
- # Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail (or pwblock returns '')
134
- if try_sasl and sasl_bind(bind_dn, options)
135
- @logger.info {'Bound SASL'}
136
- elsif simple_bind(bind_dn, options)
137
- @logger.info {'Bound simple'}
138
- elsif allow_anonymous and bind_as_anonymous(options)
139
- @logger.info {'Bound anonymous'}
140
- else
141
- if @connection.err.zero?
142
- message = 'All authentication methods exhausted.'
143
- else
144
- message = LDAP.err2string(@connection.err)
145
- end
146
- raise AuthenticationError, message
147
- end
148
-
149
- bound?
150
- end
151
-
152
- def bind_as_anonymous(options={})
153
- @logger.info {"Attempting anonymous authentication"}
154
- operation(options) do
155
- @connection.bind
156
- true
157
- end
158
- end
159
-
160
- def connecting?
161
- not @connection.nil?
162
- end
163
-
164
- def bound?
165
- connecting? and @connection.bound?
166
- end
167
-
168
- # search
169
- #
170
- # Wraps Ruby/LDAP connection.search to make it easier to search for
171
- # specific data without cracking open Base.connection
172
- def search(options={})
173
- filter = options[:filter] || 'objectClass=*'
174
- attrs = options[:attributes] || []
175
- scope = ensure_scope(options[:scope])
176
- base = options[:base]
177
- limit = options[:limit] || 0
178
- limit = nil if limit <= 0
179
-
180
- values = []
181
- attrs = attrs.to_a # just in case
182
-
183
- begin
184
- operation(options) do
185
- i = 0
186
- @connection.search(base, scope, filter, attrs) do |m|
187
- i += 1
188
- attributes = {}
189
- m.attrs.each do |attr|
190
- attributes[attr] = m.vals(attr)
191
- end
192
- value = [m.dn, attributes]
193
- value = yield(value) if block_given?
194
- values.push(value)
195
- break if limit and limit >= i
196
- end
197
- end
198
- rescue LDAP::Error
199
- # Do nothing on failure
200
- @logger.debug {"Ignore error #{$!.class}(#{$!.message}) " +
201
- "for #{filter} and attrs #{attrs.inspect}"}
202
- rescue RuntimeError
203
- if $!.message == "no result returned by search"
204
- @logger.debug {"No matches for #{filter} and attrs " +
205
- "#{attrs.inspect}"}
206
- else
207
- raise
208
- end
209
- end
210
-
211
- values
212
- end
213
-
214
- def to_ldif(dn, attributes)
215
- ldif = LDAP::LDIF.to_ldif("dn", [dn.dup])
216
- attributes.sort_by do |key, value|
217
- key
218
- end.each do |key, values|
219
- ldif << LDAP::LDIF.to_ldif(key, values)
220
- end
221
- ldif
222
- end
223
-
224
- def load(ldifs, options={})
225
- operation(options) do
226
- ldifs.split(/(?:\r?\n){2,}/).each do |ldif|
227
- LDAP::LDIF.parse_entry(ldif).send(@connection)
228
- end
229
- end
230
- end
231
-
232
- def delete(targets, options={})
233
- targets = [targets] unless targets.is_a?(Array)
234
- return if targets.empty?
235
- target = nil
236
- begin
237
- operation(options) do
238
- targets.each do |target|
239
- @connection.delete(target)
240
- end
241
- end
242
- rescue LDAP::NoSuchObject
243
- raise EntryNotFound, "No such entry: #{target}"
244
- end
245
- end
246
-
247
- def add(dn, entries, options={})
248
- begin
249
- operation(options) do
250
- @connection.add(dn, parse_entries(entries))
251
- end
252
- rescue LDAP::NoSuchObject
253
- raise EntryNotFound, "No such entry: #{dn}"
254
- rescue LDAP::InvalidDnSyntax
255
- raise DistinguishedNameInvalid.new(dn)
256
- rescue LDAP::AlreadyExists
257
- raise EntryAlreadyExist, "#{$!.message}: #{dn}"
258
- rescue LDAP::StrongAuthRequired
259
- raise StrongAuthenticationRequired, "#{$!.message}: #{dn}"
260
- rescue LDAP::ObjectClassViolation
261
- raise RequiredAttributeMissed, "#{$!.message}: #{dn}"
262
- rescue LDAP::UnwillingToPerform
263
- raise UnwillingToPerform, "#{$!.message}: #{dn}"
264
- end
265
- end
266
-
267
- def modify(dn, entries, options={})
268
- begin
269
- operation(options) do
270
- @connection.modify(dn, parse_entries(entries))
271
- end
272
- rescue LDAP::UndefinedType
273
- raise
274
- rescue LDAP::ObjectClassViolation
275
- raise RequiredAttributeMissed, "#{$!.message}: #{dn}"
276
- end
277
- end
278
-
279
- private
280
- def operation(options={}, &block)
281
- reconnect_if_need
282
- try_reconnect = !options.has_key?(:try_reconnect) ||
283
- options[:try_reconnect]
284
- with_timeout(try_reconnect) do
285
- begin
286
- block.call
287
- rescue LDAP::ResultError
288
- raise *LDAP::err2exception(@connection.err) if @connection.err != 0
289
- raise
290
- end
291
- end
292
- end
293
-
294
- def with_timeout(try_reconnect=true, &block)
295
- begin
296
- super
297
- rescue LDAP::ServerDown => e
298
- @logger.error {"#{e.class} exception occurred in with_timeout block"}
299
- @logger.error {"Exception message: #{e.message}"}
300
- @logger.error {"Exception backtrace: #{e.backtrace}"}
301
- retry if try_reconnect and reconnect
302
- raise ConnectionError.new(e.message)
303
- end
304
- end
305
-
306
- def ensure_method(method)
307
- Method.constants.each do |name|
308
- if method.to_s.downcase == name.downcase
309
- return Method.const_get(name).new
310
- end
311
- end
312
-
313
- available_methods = Method.constants.collect do |name|
314
- name.downcase.to_sym.inspect
315
- end.join(", ")
316
- raise ConfigurationError,
317
- "#{method.inspect} is not one of the available connect " +
318
- "methods #{available_methods}"
319
- end
320
-
321
- def ensure_scope(scope)
322
- value = SCOPE[scope || :sub]
323
- if value.nil?
324
- available_scopes = SCOPE.keys.collect {|s| s.inspect}
325
- raise ArgumentError, "#{scope.inspect} is not one of the available " +
326
- "LDAP scope #{available_scopes}"
327
- end
328
- value
329
- end
330
-
331
- # Bind to LDAP with the given DN using any available SASL methods
332
- def sasl_bind(bind_dn, options={})
333
- # Get all SASL mechanisms
334
- #
335
- mechanisms = nil
336
- exc = ConnectionError.new('Root DSE query failed')
337
- mechanisms = operation do
338
- @connection.root_dse[0]['supportedSASLMechanisms']
339
- end
340
-
341
- # Use GSSAPI if available
342
- # Currently only GSSAPI is supported with Ruby/LDAP from
343
- # http://caliban.org/files/redhat/RPMS/i386/ruby-ldap-0.8.2-4.i386.rpm
344
- # TODO: Investigate further SASL support
345
- return false unless (mechanisms || []).include?('GSSAPI')
346
- operation do
347
- @connection.sasl_quiet = @sasl_quiet unless @sasl_quit.nil?
348
- @connection.sasl_bind(bind_dn, 'GSSAPI')
349
- true
350
- end
351
- end
352
-
353
- # Bind to LDAP with the given DN and password
354
- def simple_bind(bind_dn, options={})
355
- # Bail if we have no password or password block
356
- if @password.nil? and @password_block.nil?
357
- @logger.error {'Skipping simple bind: ' +
358
- '@password_block and @password options are empty.'}
359
- return false
360
- end
361
-
362
- if @password
363
- password = @password
364
- else
365
- # TODO: Give a warning to reconnect users with password clearing
366
- # Get the passphrase for the first time, or anew if we aren't storing
367
- unless @password_block.respond_to?(:call)
368
- @logger.error {'Skipping simple bind: ' +
369
- '@password_block not nil or Proc object. Ignoring.'}
370
- return false
371
- end
372
- password = @password_block.call(bind_dn)
373
- end
374
-
375
- # Store the password for quick reference later
376
- @password = @store_password ? password : nil
377
-
378
- begin
379
- operation do
380
- @connection.bind(bind_dn, password)
381
- true
382
- end
383
- rescue LDAP::InvalidDnSyntax
384
- @logger.debug {"DN is invalid: #{bind_dn}"}
385
- raise DistinguishedNameInvalid.new(bind_dn)
386
- rescue LDAP::InvalidCredentials
387
- false
388
- end
389
- end
390
-
391
- def parse_entries(entries)
392
- result = []
393
- entries.each do |type, key, attributes|
394
- mod_type = ensure_mod_type(type)
395
- binary = schema.binary?(key)
396
- mod_type |= LDAP::LDAP_MOD_BVALUES if binary
397
- attributes.each do |name, values|
398
- result << LDAP.mod(mod_type, name, values)
399
- end
400
- end
401
- result
402
- end
403
-
404
- def ensure_mod_type(type)
405
- case type
406
- when :replace, :add
407
- LDAP.const_get("LDAP_MOD_#{type.to_s.upcase}")
408
- else
409
- raise ArgumentError, "unknown type: #{type}"
410
- end
411
- end
412
-
413
- # Attempts to reconnect up to the number of times allowed
414
- # If forced, try once then fail with ConnectionError if not connected.
415
- def reconnect(options={})
416
- options = options.dup
417
- force = options[:force]
418
- retry_limit = options[:retry_limit] || @retry_limit
419
- retry_wait = options[:retry_wait] || @retry_wait
420
- options[:reconnect_attempts] ||= 0
421
-
422
- loop do
423
- unless can_reconnect?(options)
424
- raise ConnectionError,
425
- 'Giving up trying to reconnect to LDAP server.'
426
- end
427
-
428
- @logger.debug {'Attempting to reconnect'}
429
- disconnect!
430
-
431
- # Reset the attempts if this was forced.
432
- options[:reconnect_attempts] = 0 if force
433
- options[:reconnect_attempts] += 1 if retry_limit >= 0
434
- begin
435
- connect(options)
436
- break
437
- rescue => detail
438
- @logger.error {"Reconnect to server failed: #{detail.exception}"}
439
- @logger.error {"Reconnect to server failed backtrace: " +
440
- detail.backtrace.join("\n")}
441
- # Do not loop if forced
442
- raise ConnectionError, detail.message if force
443
- end
444
-
445
- # Sleep before looping
446
- sleep retry_wait
447
- end
448
-
449
- true
450
- end
451
-
452
- def reconnect_if_need(options={})
453
- reconnect(options) if !connecting? and can_reconnect?(options)
454
- end
455
-
456
- # Determine if we have exceed the retry limit or not.
457
- # True is reconnecting is allowed - False if not.
458
- def can_reconnect?(options={})
459
- retry_limit = options[:retry_limit] || @retry_limit
460
- reconnect_attempts = options[:reconnect_attempts] || 0
461
-
462
- retry_limit < 0 or reconnect_attempts < (retry_limit - 1)
463
- end
464
- end
465
- end
466
- end