vines 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. data/LICENSE +19 -0
  2. data/README +34 -0
  3. data/Rakefile +55 -0
  4. data/bin/vines +95 -0
  5. data/conf/certs/README +32 -0
  6. data/conf/certs/ca-bundle.crt +3987 -0
  7. data/conf/config.rb +114 -0
  8. data/lib/vines.rb +155 -0
  9. data/lib/vines/command/bcrypt.rb +12 -0
  10. data/lib/vines/command/cert.rb +49 -0
  11. data/lib/vines/command/init.rb +58 -0
  12. data/lib/vines/command/ldap.rb +35 -0
  13. data/lib/vines/command/restart.rb +12 -0
  14. data/lib/vines/command/schema.rb +24 -0
  15. data/lib/vines/command/start.rb +28 -0
  16. data/lib/vines/command/stop.rb +18 -0
  17. data/lib/vines/config.rb +191 -0
  18. data/lib/vines/contact.rb +99 -0
  19. data/lib/vines/daemon.rb +78 -0
  20. data/lib/vines/error.rb +150 -0
  21. data/lib/vines/jid.rb +56 -0
  22. data/lib/vines/kit.rb +23 -0
  23. data/lib/vines/router.rb +125 -0
  24. data/lib/vines/stanza.rb +55 -0
  25. data/lib/vines/stanza/iq.rb +50 -0
  26. data/lib/vines/stanza/iq/auth.rb +18 -0
  27. data/lib/vines/stanza/iq/disco_info.rb +25 -0
  28. data/lib/vines/stanza/iq/disco_items.rb +23 -0
  29. data/lib/vines/stanza/iq/error.rb +16 -0
  30. data/lib/vines/stanza/iq/ping.rb +16 -0
  31. data/lib/vines/stanza/iq/query.rb +10 -0
  32. data/lib/vines/stanza/iq/result.rb +16 -0
  33. data/lib/vines/stanza/iq/roster.rb +153 -0
  34. data/lib/vines/stanza/iq/session.rb +22 -0
  35. data/lib/vines/stanza/iq/vcard.rb +58 -0
  36. data/lib/vines/stanza/message.rb +41 -0
  37. data/lib/vines/stanza/presence.rb +119 -0
  38. data/lib/vines/stanza/presence/error.rb +23 -0
  39. data/lib/vines/stanza/presence/probe.rb +38 -0
  40. data/lib/vines/stanza/presence/subscribe.rb +66 -0
  41. data/lib/vines/stanza/presence/subscribed.rb +64 -0
  42. data/lib/vines/stanza/presence/unavailable.rb +15 -0
  43. data/lib/vines/stanza/presence/unsubscribe.rb +57 -0
  44. data/lib/vines/stanza/presence/unsubscribed.rb +50 -0
  45. data/lib/vines/storage.rb +216 -0
  46. data/lib/vines/storage/couchdb.rb +119 -0
  47. data/lib/vines/storage/ldap.rb +59 -0
  48. data/lib/vines/storage/local.rb +66 -0
  49. data/lib/vines/storage/redis.rb +108 -0
  50. data/lib/vines/storage/sql.rb +174 -0
  51. data/lib/vines/store.rb +51 -0
  52. data/lib/vines/stream.rb +198 -0
  53. data/lib/vines/stream/client.rb +131 -0
  54. data/lib/vines/stream/client/auth.rb +94 -0
  55. data/lib/vines/stream/client/auth_restart.rb +33 -0
  56. data/lib/vines/stream/client/bind.rb +58 -0
  57. data/lib/vines/stream/client/bind_restart.rb +25 -0
  58. data/lib/vines/stream/client/closed.rb +13 -0
  59. data/lib/vines/stream/client/ready.rb +15 -0
  60. data/lib/vines/stream/client/start.rb +27 -0
  61. data/lib/vines/stream/client/tls.rb +37 -0
  62. data/lib/vines/stream/component.rb +53 -0
  63. data/lib/vines/stream/component/handshake.rb +25 -0
  64. data/lib/vines/stream/component/ready.rb +24 -0
  65. data/lib/vines/stream/component/start.rb +19 -0
  66. data/lib/vines/stream/http.rb +111 -0
  67. data/lib/vines/stream/http/http_request.rb +22 -0
  68. data/lib/vines/stream/http/http_state.rb +139 -0
  69. data/lib/vines/stream/http/http_states.rb +53 -0
  70. data/lib/vines/stream/parser.rb +78 -0
  71. data/lib/vines/stream/server.rb +126 -0
  72. data/lib/vines/stream/server/auth.rb +13 -0
  73. data/lib/vines/stream/server/auth_restart.rb +19 -0
  74. data/lib/vines/stream/server/final_restart.rb +20 -0
  75. data/lib/vines/stream/server/outbound/auth.rb +31 -0
  76. data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
  77. data/lib/vines/stream/server/outbound/auth_result.rb +28 -0
  78. data/lib/vines/stream/server/outbound/final_features.rb +27 -0
  79. data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
  80. data/lib/vines/stream/server/outbound/start.rb +20 -0
  81. data/lib/vines/stream/server/outbound/tls.rb +30 -0
  82. data/lib/vines/stream/server/outbound/tls_result.rb +31 -0
  83. data/lib/vines/stream/server/ready.rb +20 -0
  84. data/lib/vines/stream/server/start.rb +13 -0
  85. data/lib/vines/stream/server/tls.rb +13 -0
  86. data/lib/vines/stream/state.rb +55 -0
  87. data/lib/vines/token_bucket.rb +46 -0
  88. data/lib/vines/user.rb +124 -0
  89. data/lib/vines/version.rb +5 -0
  90. data/lib/vines/xmpp_server.rb +25 -0
  91. data/test/config_test.rb +396 -0
  92. data/test/error_test.rb +59 -0
  93. data/test/ext/nokogiri.rb +14 -0
  94. data/test/jid_test.rb +71 -0
  95. data/test/kit_test.rb +21 -0
  96. data/test/router_test.rb +60 -0
  97. data/test/stanza/iq/roster_test.rb +198 -0
  98. data/test/stanza/iq/session_test.rb +30 -0
  99. data/test/stanza/iq/vcard_test.rb +159 -0
  100. data/test/stanza/message_test.rb +124 -0
  101. data/test/stanza/presence/subscribe_test.rb +75 -0
  102. data/test/storage/couchdb_test.rb +102 -0
  103. data/test/storage/ldap_test.rb +207 -0
  104. data/test/storage/local_test.rb +54 -0
  105. data/test/storage/redis_test.rb +75 -0
  106. data/test/storage/sql_test.rb +55 -0
  107. data/test/storage/storage_tests.rb +134 -0
  108. data/test/storage_test.rb +90 -0
  109. data/test/stream/client/auth_test.rb +127 -0
  110. data/test/stream/client/ready_test.rb +47 -0
  111. data/test/stream/component/handshake_test.rb +46 -0
  112. data/test/stream/component/ready_test.rb +105 -0
  113. data/test/stream/component/start_test.rb +41 -0
  114. data/test/stream/parser_test.rb +121 -0
  115. data/test/stream/server/outbound/auth_test.rb +77 -0
  116. data/test/stream/server/ready_test.rb +100 -0
  117. data/test/token_bucket_test.rb +24 -0
  118. data/test/user_test.rb +64 -0
  119. metadata +318 -0
@@ -0,0 +1,124 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'vines'
4
+ require 'ext/nokogiri'
5
+ require 'minitest/mock'
6
+ require 'test/unit'
7
+
8
+ class MessageTest < Test::Unit::TestCase
9
+ def setup
10
+ @stream = MiniTest::Mock.new
11
+ end
12
+
13
+ def test_bad_type_returns_error
14
+ node = node('<message type="bogus">hello!</message>')
15
+ stanza = Vines::Stanza::Message.new(node, @stream)
16
+ assert_raise(Vines::StanzaErrors::BadRequest) { stanza.process }
17
+ end
18
+
19
+ def test_missing_to_address_is_sent_to_sender
20
+ alice = Vines::User.new(:jid => 'alice@wonderland.lit/tea')
21
+ node = node('<message>hello!</message>')
22
+
23
+ recipient = MiniTest::Mock.new
24
+ router = MiniTest::Mock.new
25
+ router.expect(:local?, true, [node])
26
+ router.expect(:connected_resources, [recipient], [alice.jid.bare])
27
+
28
+ @stream.expect(:router, router)
29
+ @stream.expect(:user, alice)
30
+ @stream.expect(:broadcast, nil, [node, [recipient]])
31
+
32
+ stanza = Vines::Stanza::Message.new(node, @stream)
33
+ assert_nothing_raised { stanza.process }
34
+ assert @stream.verify
35
+ assert router.verify
36
+ assert recipient.verify
37
+ end
38
+
39
+ def test_message_to_non_user_is_ignored
40
+ bogus = Vines::JID.new('bogus@wonderland.lit/cake')
41
+ node = node(%Q{<message to="#{bogus}">hello!</message>})
42
+
43
+ router = MiniTest::Mock.new
44
+ router.expect(:local?, true, [node])
45
+ router.expect(:connected_resources, [], [bogus])
46
+
47
+ storage = MiniTest::Mock.new
48
+ storage.expect(:find_user, nil, [bogus])
49
+
50
+ @stream.expect(:router, router)
51
+ @stream.expect(:storage, storage, [bogus.domain])
52
+
53
+ stanza = Vines::Stanza::Message.new(node, @stream)
54
+ assert_nothing_raised { stanza.process }
55
+ assert @stream.verify
56
+ assert router.verify
57
+ assert storage.verify
58
+ end
59
+
60
+ def test_message_to_offline_user_returns_error
61
+ hatter = Vines::User.new(:jid => 'hatter@wonderland.lit/cake')
62
+ node = node(%Q{<message to="#{hatter.jid}">hello!</message>})
63
+
64
+ router = MiniTest::Mock.new
65
+ router.expect(:local?, true, [node])
66
+ router.expect(:connected_resources, [], [hatter.jid])
67
+
68
+ storage = MiniTest::Mock.new
69
+ storage.expect(:find_user, hatter, [hatter.jid])
70
+
71
+ @stream.expect(:router, router)
72
+ @stream.expect(:storage, storage, [hatter.jid.domain])
73
+
74
+ stanza = Vines::Stanza::Message.new(node, @stream)
75
+ assert_raise(Vines::StanzaErrors::ServiceUnavailable) { stanza.process }
76
+ assert @stream.verify
77
+ assert router.verify
78
+ assert storage.verify
79
+ end
80
+
81
+ def test_message_to_local_user_in_different_domain_is_delivered
82
+ romeo = Vines::User.new(:jid => 'romeo@verona.lit/balcony')
83
+ node = node(%Q{<message to="#{romeo.jid}">hello!</message>})
84
+
85
+ recipient = MiniTest::Mock.new
86
+ router = MiniTest::Mock.new
87
+ router.expect(:local?, true, [node])
88
+ router.expect(:connected_resources, [recipient], [romeo.jid])
89
+
90
+ @stream.expect(:router, router)
91
+ @stream.expect(:broadcast, nil, [node, [recipient]])
92
+
93
+ stanza = Vines::Stanza::Message.new(node, @stream)
94
+ assert_nothing_raised { stanza.process }
95
+ assert @stream.verify
96
+ assert router.verify
97
+ assert recipient.verify
98
+ end
99
+
100
+ def test_message_to_remote_user_is_routed
101
+ alice = Vines::User.new(:jid => 'alice@wonderland.lit/tea')
102
+ romeo = Vines::User.new(:jid => 'romeo@verona.lit/balcony')
103
+ node = node(%Q{<message to="#{romeo.jid}">hello!</message>})
104
+ expected = node(%Q{<message to="#{romeo.jid}" from="#{alice.jid}">hello!</message>})
105
+
106
+ router = MiniTest::Mock.new
107
+ router.expect(:local?, false, [node])
108
+ router.expect(:route, nil, [expected])
109
+
110
+ @stream.expect(:router, router)
111
+ @stream.expect(:user, alice)
112
+
113
+ stanza = Vines::Stanza::Message.new(node, @stream)
114
+ assert_nothing_raised { stanza.process }
115
+ assert @stream.verify
116
+ assert router.verify
117
+ end
118
+
119
+ private
120
+
121
+ def node(xml)
122
+ Nokogiri::XML(xml).root
123
+ end
124
+ end
@@ -0,0 +1,75 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'vines'
4
+ require 'ext/nokogiri'
5
+ require 'minitest/mock'
6
+ require 'test/unit'
7
+
8
+ class SubscribeTest < Test::Unit::TestCase
9
+ def test_outbound_subscribe_to_local_jid_but_missing_contact
10
+ alice = Vines::JID.new('alice@wonderland.lit/tea')
11
+ hatter = Vines::JID.new('hatter@wonderland.lit')
12
+
13
+ contact = Vines::Contact.new(:jid => hatter)
14
+
15
+ user = MiniTest::Mock.new
16
+ user.expect(:jid, alice)
17
+ user.expect(:request_subscription, nil, [hatter.to_s])
18
+ user.expect(:contact, contact, [hatter])
19
+
20
+ storage = MiniTest::Mock.new
21
+ storage.expect(:save_user, nil, [user])
22
+ storage.expect(:find_user, nil, [hatter])
23
+
24
+ recipient = MiniTest::Mock.new
25
+ recipient.expect(:user, Vines::User.new(:jid => hatter))
26
+ def recipient.nodes; @nodes; end
27
+ def recipient.write(node)
28
+ @nodes ||= []
29
+ @nodes << node
30
+ end
31
+
32
+ router = MiniTest::Mock.new
33
+ router.expect(:interested_resources, [recipient], [alice])
34
+
35
+ stream = MiniTest::Mock.new
36
+ stream.expect(:domain, 'wonderland.lit')
37
+ stream.expect(:storage, storage, ['wonderland.lit'])
38
+ stream.expect(:user, user)
39
+ stream.expect(:router, router)
40
+ stream.expect(:update_user_streams, nil, [user])
41
+ def stream.nodes; @nodes; end
42
+ def stream.write(node)
43
+ @nodes ||= []
44
+ @nodes << node
45
+ end
46
+
47
+ node = node(%q{<presence id="42" to="hatter@wonderland.lit" type="subscribe"/>})
48
+ stanza = Vines::Stanza::Presence::Subscribe.new(node, stream)
49
+ def stanza.route_iq; false; end
50
+ def stanza.inbound?; false; end
51
+ def stanza.local?; true; end
52
+
53
+ stanza.process
54
+ assert stream.verify
55
+ assert user.verify
56
+ assert storage.verify
57
+ assert router.verify
58
+ assert_equal 1, stream.nodes.size
59
+ assert_equal 1, recipient.nodes.size
60
+
61
+ expected = node(%q{<presence from="hatter@wonderland.lit" id="42" to="alice@wonderland.lit" type="unsubscribed"/>})
62
+ assert_equal expected, stream.nodes[0]
63
+
64
+ query = %q{<query xmlns="jabber:iq:roster"><item jid="hatter@wonderland.lit" subscription="none"/></query>}
65
+ expected = node(%Q{<iq to="alice@wonderland.lit/tea" type="set">#{query}</iq>})
66
+ recipient.nodes[0].remove_attribute('id') # id is random
67
+ assert_equal expected, recipient.nodes[0]
68
+ end
69
+
70
+ private
71
+
72
+ def node(xml)
73
+ Nokogiri::XML(xml).root
74
+ end
75
+ end
@@ -0,0 +1,102 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'storage_tests'
4
+ require 'vines'
5
+ require 'test/unit'
6
+
7
+ class CouchDBTest < Test::Unit::TestCase
8
+ include StorageTests
9
+
10
+ URL = 'http://localhost:5984/xmpp_testcase'.freeze
11
+
12
+ def setup
13
+ EMLoop.new do
14
+ database(:put)
15
+ save_doc({'_id' => 'user:empty@wonderland.lit'})
16
+
17
+ save_doc({
18
+ '_id' => 'user:no_password@wonderland.lit',
19
+ 'type' => 'User',
20
+ 'foo' => 'bar'})
21
+
22
+ save_doc({
23
+ '_id' => 'user:clear_password@wonderland.lit',
24
+ 'type' => 'User',
25
+ 'password' => 'secret'})
26
+
27
+ save_doc({
28
+ '_id' => 'user:bcrypt_password@wonderland.lit',
29
+ 'type' => 'User',
30
+ 'password' => BCrypt::Password.create('secret')})
31
+
32
+ save_doc({
33
+ '_id' => 'user:full@wonderland.lit',
34
+ 'type' => 'User',
35
+ 'password' => BCrypt::Password.create('secret'),
36
+ 'name' => 'Tester',
37
+ 'roster' => {
38
+ 'contact1@wonderland.lit' => {
39
+ 'name' => 'Contact1',
40
+ 'groups' => %w[Group1 Group2]
41
+ },
42
+ 'contact2@wonderland.lit' => {
43
+ 'name' => 'Contact2',
44
+ 'groups' => %w[Group3 Group4]
45
+ }
46
+ }
47
+ })
48
+
49
+ save_doc({
50
+ '_id' => 'vcard:full@wonderland.lit',
51
+ 'type' => 'Vcard',
52
+ 'card' => StorageTests::VCARD.to_xml
53
+ })
54
+ end
55
+ end
56
+
57
+ def teardown
58
+ EMLoop.new do
59
+ database(:delete)
60
+ end
61
+ end
62
+
63
+ def save_doc(doc)
64
+ fiber = Fiber.current
65
+ http = EM::HttpRequest.new(URL).post(
66
+ :head => {'Content-Type' => 'application/json'},
67
+ :body => doc.to_json)
68
+ http.callback { fiber.resume }
69
+ http.errback { raise 'save_doc failed' }
70
+ Fiber.yield
71
+ end
72
+
73
+ def database(method=:put)
74
+ fiber = Fiber.current
75
+ http = EM::HttpRequest.new(URL).send(method)
76
+ http.callback { fiber.resume }
77
+ http.errback { raise "#{method} database failed" }
78
+ Fiber.yield
79
+ end
80
+
81
+ def storage
82
+ Vines::Storage::CouchDB.new do
83
+ host 'localhost'
84
+ port 5984
85
+ database 'xmpp_testcase'
86
+ end
87
+ end
88
+
89
+ def test_init
90
+ EMLoop.new do
91
+ assert_raise(RuntimeError) { Vines::Storage::CouchDB.new {} }
92
+ assert_raise(RuntimeError) { Vines::Storage::CouchDB.new { host 'localhost' } }
93
+ assert_nothing_raised do
94
+ Vines::Storage::CouchDB.new do
95
+ host 'localhost'
96
+ port '5984'
97
+ database 'test'
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,207 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'vines'
4
+ require 'minitest/mock'
5
+ require 'test/unit'
6
+
7
+ class LdapTest < Test::Unit::TestCase
8
+ ALICE_DN = 'uid=alice@wondlerand.lit,ou=People,dc=wonderland,dc=lit'
9
+ CONTEXT = {}
10
+
11
+ def setup
12
+ end
13
+
14
+ def teardown
15
+ CONTEXT.clear
16
+ end
17
+
18
+ def test_missing_host_and_port
19
+ assert_raises(RuntimeError) do
20
+ Vines::Storage::Ldap.new(nil, nil) do
21
+ tls true
22
+ dn 'cn=Directory Manager'
23
+ password 'secr3t'
24
+ basedn 'dc=wonderland,dc=lit'
25
+ object_class 'person'
26
+ user_attr 'uid'
27
+ name_attr 'cn'
28
+ end
29
+ end
30
+ end
31
+
32
+ def test_default_host_and_port
33
+ assert_nothing_raised do
34
+ Vines::Storage::Ldap.new do
35
+ tls true
36
+ dn 'cn=Directory Manager'
37
+ password 'secr3t'
38
+ basedn 'dc=wonderland,dc=lit'
39
+ object_class 'person'
40
+ user_attr 'uid'
41
+ name_attr 'cn'
42
+ end
43
+ end
44
+ end
45
+
46
+ def test_configured_host_and_port
47
+ assert_nothing_raised do
48
+ Vines::Storage::Ldap.new('0.0.0.1', 42) do
49
+ tls true
50
+ dn 'cn=Directory Manager'
51
+ password 'secr3t'
52
+ basedn 'dc=wonderland,dc=lit'
53
+ object_class 'person'
54
+ user_attr 'uid'
55
+ name_attr 'cn'
56
+ end
57
+ end
58
+ end
59
+
60
+ def test_missing_parameters
61
+ assert_raises(RuntimeError) do
62
+ Vines::Storage::Ldap.new {}
63
+ end
64
+ end
65
+
66
+ def test_blank_parameters
67
+ assert_raises(RuntimeError) do
68
+ Vines::Storage::Ldap.new do
69
+ tls
70
+ dn
71
+ password
72
+ basedn
73
+ object_class
74
+ user_attr
75
+ name_attr
76
+ end
77
+ end
78
+ end
79
+
80
+ # Make sure we properly handle boolean false values.
81
+ def test_false_tls
82
+ assert_nothing_raised do
83
+ Vines::Storage::Ldap.new do
84
+ tls false
85
+ dn 'cn=Directory Manager'
86
+ password 'secr3t'
87
+ basedn 'dc=wonderland,dc=lit'
88
+ object_class 'person'
89
+ user_attr 'uid'
90
+ name_attr 'cn'
91
+ end
92
+ end
93
+ end
94
+
95
+ def test_authenticate_with_missing_credentials
96
+ ldap = connect
97
+ assert_nil ldap.authenticate(nil, nil)
98
+ assert_nil ldap.authenticate('', '')
99
+ assert_nil ldap.authenticate('user', ' ')
100
+ end
101
+
102
+ def test_authenticate_user_not_found
103
+ CONTEXT.merge!({
104
+ :mock_ldap => MiniTest::Mock.new,
105
+ :connect_calls => 0
106
+ })
107
+ ldap = connect
108
+ def ldap.connect(dn, password)
109
+ CONTEXT[:connect_calls] += 1
110
+ raise unless 'cn=Directory Manager' == dn
111
+ raise unless 'secr3t' == password
112
+
113
+ clas = Net::LDAP::Filter.eq('objectClass', 'person')
114
+ uid = Net::LDAP::Filter.eq('uid', 'alice@wonderland.lit')
115
+
116
+ mock = CONTEXT[:mock_ldap]
117
+ mock.expect(:search, [], [{:attributes => ['cn', 'mail'], :filter => clas & uid}])
118
+ end
119
+ assert_nil ldap.authenticate('alice@wonderland.lit', 'passw0rd')
120
+ assert CONTEXT[:mock_ldap]
121
+ assert_equal 1, CONTEXT[:connect_calls]
122
+ end
123
+
124
+ def test_authenticate_user_fails_bind
125
+ CONTEXT.merge!({
126
+ :credentials => [
127
+ {:dn => 'cn=Directory Manager', :password => 'secr3t'},
128
+ {:dn => ALICE_DN, :password => 'passw0rd'}
129
+ ],
130
+ :mock_ldap => MiniTest::Mock.new,
131
+ :mock_entry => MiniTest::Mock.new,
132
+ :connect_calls => 0
133
+ })
134
+ ldap = connect
135
+ def ldap.connect(dn, password)
136
+ credentials = CONTEXT[:credentials][CONTEXT[:connect_calls]]
137
+ CONTEXT[:connect_calls] += 1
138
+ raise unless credentials[:dn] == dn && credentials[:password] == password
139
+
140
+ clas = Net::LDAP::Filter.eq('objectClass', 'person')
141
+ uid = Net::LDAP::Filter.eq('uid', 'alice@wonderland.lit')
142
+
143
+ entry = CONTEXT[:mock_entry]
144
+ entry.expect(:dn, ALICE_DN)
145
+
146
+ mock = CONTEXT[:mock_ldap]
147
+ mock.expect(:search, [entry], [{:attributes => ['cn', 'mail'], :filter => clas & uid}])
148
+ mock.expect(:bind, false)
149
+ end
150
+ assert_nil ldap.authenticate('alice@wonderland.lit', 'passw0rd')
151
+ assert_equal 2, CONTEXT[:connect_calls]
152
+ assert CONTEXT[:mock_entry].verify
153
+ assert CONTEXT[:mock_ldap].verify
154
+ end
155
+
156
+ def test_authenticate_success
157
+ CONTEXT.merge!({
158
+ :credentials => [
159
+ {:dn => 'cn=Directory Manager', :password => 'secr3t'},
160
+ {:dn => ALICE_DN, :password => 'passw0rd'}
161
+ ],
162
+ :mock_ldap => MiniTest::Mock.new,
163
+ :mock_entry => MiniTest::Mock.new,
164
+ :connect_calls => 0
165
+ })
166
+ ldap = connect
167
+ def ldap.connect(dn, password)
168
+ credentials = CONTEXT[:credentials][CONTEXT[:connect_calls]]
169
+ CONTEXT[:connect_calls] += 1
170
+ raise unless credentials[:dn] == dn && credentials[:password] == password
171
+
172
+ clas = Net::LDAP::Filter.eq('objectClass', 'person')
173
+ uid = Net::LDAP::Filter.eq('uid', 'alice@wonderland.lit')
174
+
175
+ entry = CONTEXT[:mock_entry]
176
+ entry.expect(:dn, ALICE_DN)
177
+ entry.expect(:[], ['Alice Liddell'], ['cn'])
178
+
179
+ mock = CONTEXT[:mock_ldap]
180
+ mock.expect(:search, [entry], [{:attributes => ['cn', 'mail'], :filter => clas & uid}])
181
+ mock.expect(:bind, true)
182
+ end
183
+ user = ldap.authenticate('alice@wonderland.lit', 'passw0rd')
184
+ assert_not_nil user
185
+ assert_equal 'alice@wonderland.lit', user.jid.to_s
186
+ assert_equal 'Alice Liddell', user.name
187
+ assert_equal [], user.roster
188
+
189
+ assert_equal 2, CONTEXT[:connect_calls]
190
+ assert CONTEXT[:mock_entry].verify
191
+ assert CONTEXT[:mock_ldap].verify
192
+ end
193
+
194
+ private
195
+
196
+ def connect
197
+ Vines::Storage::Ldap.new('0.0.0.0', 636) do
198
+ tls true
199
+ dn 'cn=Directory Manager'
200
+ password 'secr3t'
201
+ basedn 'dc=wonderland,dc=lit'
202
+ object_class 'person'
203
+ user_attr 'uid'
204
+ name_attr 'cn'
205
+ end
206
+ end
207
+ end