vines 0.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.
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