lygneo-vines 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (174) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +19 -0
  4. data/README.md +7 -0
  5. data/Rakefile +23 -0
  6. data/bin/vines +4 -0
  7. data/conf/certs/README +39 -0
  8. data/conf/certs/ca-bundle.crt +3895 -0
  9. data/conf/config.rb +42 -0
  10. data/lib/vines/cli.rb +132 -0
  11. data/lib/vines/cluster/connection.rb +26 -0
  12. data/lib/vines/cluster/publisher.rb +55 -0
  13. data/lib/vines/cluster/pubsub.rb +92 -0
  14. data/lib/vines/cluster/sessions.rb +125 -0
  15. data/lib/vines/cluster/subscriber.rb +108 -0
  16. data/lib/vines/cluster.rb +246 -0
  17. data/lib/vines/command/bcrypt.rb +12 -0
  18. data/lib/vines/command/cert.rb +50 -0
  19. data/lib/vines/command/init.rb +68 -0
  20. data/lib/vines/command/ldap.rb +38 -0
  21. data/lib/vines/command/restart.rb +12 -0
  22. data/lib/vines/command/schema.rb +24 -0
  23. data/lib/vines/command/start.rb +28 -0
  24. data/lib/vines/command/stop.rb +18 -0
  25. data/lib/vines/config/host.rb +125 -0
  26. data/lib/vines/config/port.rb +132 -0
  27. data/lib/vines/config/pubsub.rb +108 -0
  28. data/lib/vines/config.rb +223 -0
  29. data/lib/vines/daemon.rb +78 -0
  30. data/lib/vines/error.rb +150 -0
  31. data/lib/vines/follower.rb +111 -0
  32. data/lib/vines/jid.rb +95 -0
  33. data/lib/vines/kit.rb +23 -0
  34. data/lib/vines/log.rb +24 -0
  35. data/lib/vines/router.rb +179 -0
  36. data/lib/vines/stanza/iq/auth.rb +18 -0
  37. data/lib/vines/stanza/iq/disco_info.rb +45 -0
  38. data/lib/vines/stanza/iq/disco_items.rb +29 -0
  39. data/lib/vines/stanza/iq/error.rb +16 -0
  40. data/lib/vines/stanza/iq/ping.rb +16 -0
  41. data/lib/vines/stanza/iq/private_storage.rb +83 -0
  42. data/lib/vines/stanza/iq/query.rb +10 -0
  43. data/lib/vines/stanza/iq/result.rb +16 -0
  44. data/lib/vines/stanza/iq/roster.rb +140 -0
  45. data/lib/vines/stanza/iq/session.rb +17 -0
  46. data/lib/vines/stanza/iq/vcard.rb +56 -0
  47. data/lib/vines/stanza/iq/version.rb +25 -0
  48. data/lib/vines/stanza/iq.rb +48 -0
  49. data/lib/vines/stanza/message.rb +40 -0
  50. data/lib/vines/stanza/presence/error.rb +23 -0
  51. data/lib/vines/stanza/presence/probe.rb +37 -0
  52. data/lib/vines/stanza/presence/subscribe.rb +42 -0
  53. data/lib/vines/stanza/presence/subscribed.rb +51 -0
  54. data/lib/vines/stanza/presence/unavailable.rb +15 -0
  55. data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
  56. data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
  57. data/lib/vines/stanza/presence.rb +141 -0
  58. data/lib/vines/stanza/pubsub/create.rb +39 -0
  59. data/lib/vines/stanza/pubsub/delete.rb +41 -0
  60. data/lib/vines/stanza/pubsub/publish.rb +66 -0
  61. data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
  62. data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
  63. data/lib/vines/stanza/pubsub.rb +22 -0
  64. data/lib/vines/stanza.rb +175 -0
  65. data/lib/vines/storage/ldap.rb +71 -0
  66. data/lib/vines/storage/local.rb +139 -0
  67. data/lib/vines/storage/null.rb +39 -0
  68. data/lib/vines/storage/sql.rb +138 -0
  69. data/lib/vines/storage.rb +239 -0
  70. data/lib/vines/store.rb +110 -0
  71. data/lib/vines/stream/client/auth.rb +74 -0
  72. data/lib/vines/stream/client/auth_restart.rb +29 -0
  73. data/lib/vines/stream/client/bind.rb +72 -0
  74. data/lib/vines/stream/client/bind_restart.rb +24 -0
  75. data/lib/vines/stream/client/closed.rb +13 -0
  76. data/lib/vines/stream/client/ready.rb +17 -0
  77. data/lib/vines/stream/client/session.rb +210 -0
  78. data/lib/vines/stream/client/start.rb +27 -0
  79. data/lib/vines/stream/client/tls.rb +38 -0
  80. data/lib/vines/stream/client.rb +84 -0
  81. data/lib/vines/stream/component/handshake.rb +26 -0
  82. data/lib/vines/stream/component/ready.rb +23 -0
  83. data/lib/vines/stream/component/start.rb +19 -0
  84. data/lib/vines/stream/component.rb +58 -0
  85. data/lib/vines/stream/http/auth.rb +22 -0
  86. data/lib/vines/stream/http/bind.rb +32 -0
  87. data/lib/vines/stream/http/bind_restart.rb +37 -0
  88. data/lib/vines/stream/http/ready.rb +29 -0
  89. data/lib/vines/stream/http/request.rb +172 -0
  90. data/lib/vines/stream/http/session.rb +120 -0
  91. data/lib/vines/stream/http/sessions.rb +65 -0
  92. data/lib/vines/stream/http/start.rb +23 -0
  93. data/lib/vines/stream/http.rb +157 -0
  94. data/lib/vines/stream/parser.rb +79 -0
  95. data/lib/vines/stream/sasl.rb +128 -0
  96. data/lib/vines/stream/server/auth.rb +13 -0
  97. data/lib/vines/stream/server/auth_restart.rb +13 -0
  98. data/lib/vines/stream/server/final_restart.rb +21 -0
  99. data/lib/vines/stream/server/outbound/auth.rb +31 -0
  100. data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
  101. data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
  102. data/lib/vines/stream/server/outbound/final_features.rb +28 -0
  103. data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
  104. data/lib/vines/stream/server/outbound/start.rb +20 -0
  105. data/lib/vines/stream/server/outbound/tls.rb +30 -0
  106. data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
  107. data/lib/vines/stream/server/ready.rb +24 -0
  108. data/lib/vines/stream/server/start.rb +13 -0
  109. data/lib/vines/stream/server/tls.rb +13 -0
  110. data/lib/vines/stream/server.rb +150 -0
  111. data/lib/vines/stream/state.rb +60 -0
  112. data/lib/vines/stream.rb +247 -0
  113. data/lib/vines/token_bucket.rb +55 -0
  114. data/lib/vines/user.rb +123 -0
  115. data/lib/vines/version.rb +6 -0
  116. data/lib/vines/xmpp_server.rb +25 -0
  117. data/lib/vines.rb +203 -0
  118. data/test/cluster/publisher_test.rb +57 -0
  119. data/test/cluster/sessions_test.rb +47 -0
  120. data/test/cluster/subscriber_test.rb +109 -0
  121. data/test/config/host_test.rb +369 -0
  122. data/test/config/pubsub_test.rb +187 -0
  123. data/test/config_test.rb +732 -0
  124. data/test/error_test.rb +58 -0
  125. data/test/ext/nokogiri.rb +14 -0
  126. data/test/follower_test.rb +102 -0
  127. data/test/jid_test.rb +147 -0
  128. data/test/kit_test.rb +31 -0
  129. data/test/router_test.rb +243 -0
  130. data/test/stanza/iq/disco_info_test.rb +78 -0
  131. data/test/stanza/iq/disco_items_test.rb +49 -0
  132. data/test/stanza/iq/private_storage_test.rb +184 -0
  133. data/test/stanza/iq/roster_test.rb +229 -0
  134. data/test/stanza/iq/session_test.rb +25 -0
  135. data/test/stanza/iq/vcard_test.rb +146 -0
  136. data/test/stanza/iq/version_test.rb +64 -0
  137. data/test/stanza/iq_test.rb +70 -0
  138. data/test/stanza/message_test.rb +126 -0
  139. data/test/stanza/presence/probe_test.rb +50 -0
  140. data/test/stanza/presence/subscribe_test.rb +83 -0
  141. data/test/stanza/pubsub/create_test.rb +116 -0
  142. data/test/stanza/pubsub/delete_test.rb +169 -0
  143. data/test/stanza/pubsub/publish_test.rb +309 -0
  144. data/test/stanza/pubsub/subscribe_test.rb +205 -0
  145. data/test/stanza/pubsub/unsubscribe_test.rb +148 -0
  146. data/test/stanza_test.rb +85 -0
  147. data/test/storage/ldap_test.rb +201 -0
  148. data/test/storage/local_test.rb +59 -0
  149. data/test/storage/mock_redis.rb +97 -0
  150. data/test/storage/null_test.rb +29 -0
  151. data/test/storage/storage_tests.rb +182 -0
  152. data/test/storage_test.rb +85 -0
  153. data/test/store_test.rb +130 -0
  154. data/test/stream/client/auth_test.rb +137 -0
  155. data/test/stream/client/ready_test.rb +47 -0
  156. data/test/stream/client/session_test.rb +27 -0
  157. data/test/stream/component/handshake_test.rb +52 -0
  158. data/test/stream/component/ready_test.rb +103 -0
  159. data/test/stream/component/start_test.rb +39 -0
  160. data/test/stream/http/auth_test.rb +70 -0
  161. data/test/stream/http/ready_test.rb +86 -0
  162. data/test/stream/http/request_test.rb +209 -0
  163. data/test/stream/http/sessions_test.rb +49 -0
  164. data/test/stream/http/start_test.rb +50 -0
  165. data/test/stream/parser_test.rb +122 -0
  166. data/test/stream/sasl_test.rb +195 -0
  167. data/test/stream/server/auth_test.rb +61 -0
  168. data/test/stream/server/outbound/auth_test.rb +75 -0
  169. data/test/stream/server/ready_test.rb +98 -0
  170. data/test/test_helper.rb +42 -0
  171. data/test/token_bucket_test.rb +44 -0
  172. data/test/user_test.rb +96 -0
  173. data/vines.gemspec +30 -0
  174. metadata +387 -0
@@ -0,0 +1,85 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'storage_tests'
4
+ require 'test_helper'
5
+
6
+ describe Vines::Storage do
7
+ ALICE = 'alice@wonderland.lit'.freeze
8
+
9
+ class MockLdapStorage < Vines::Storage
10
+ attr_reader :authenticate_calls, :find_user_calls, :save_user_calls
11
+
12
+ def initialize(found_user=nil)
13
+ @found_user = found_user
14
+ @authenticate_calls = @find_user_calls = @save_user_calls = 0
15
+ @ldap = Class.new do
16
+ attr_accessor :user, :auth
17
+ def authenticate(username, password)
18
+ @auth ||= []
19
+ @auth << [username, password]
20
+ @user
21
+ end
22
+ end.new
23
+ end
24
+
25
+ def authenticate(username, password)
26
+ @authenticate_calls += 1
27
+ nil
28
+ end
29
+ wrap_ldap :authenticate
30
+
31
+ def find_user(jid)
32
+ @find_user_calls += 1
33
+ @found_user
34
+ end
35
+
36
+ def save_user(user)
37
+ @save_user_calls += 1
38
+ end
39
+ end
40
+
41
+ describe '#authenticate_with_ldap' do
42
+ it 'fails when given a bad password' do
43
+ StorageTests::EMLoop.new do
44
+ storage = MockLdapStorage.new
45
+ storage.ldap.user = nil
46
+ user = storage.authenticate(ALICE, 'bogus')
47
+ assert_nil user
48
+ assert_equal 0, storage.authenticate_calls
49
+ assert_equal 0, storage.find_user_calls
50
+ assert_equal 0, storage.save_user_calls
51
+ assert_equal [ALICE, 'bogus'], storage.ldap.auth.first
52
+ end
53
+ end
54
+
55
+ it 'succeeds when user exists in database' do
56
+ StorageTests::EMLoop.new do
57
+ alice = Vines::User.new(:jid => ALICE)
58
+ storage = MockLdapStorage.new(alice)
59
+ storage.ldap.user = alice
60
+ user = storage.authenticate(ALICE, 'secr3t')
61
+ refute_nil user
62
+ assert_equal ALICE, user.jid.to_s
63
+ assert_equal 0, storage.authenticate_calls
64
+ assert_equal 1, storage.find_user_calls
65
+ assert_equal 0, storage.save_user_calls
66
+ assert_equal [ALICE, 'secr3t'], storage.ldap.auth.first
67
+ end
68
+ end
69
+
70
+ it 'succeeds and saves user to the database' do
71
+ StorageTests::EMLoop.new do
72
+ alice = Vines::User.new(:jid => ALICE)
73
+ storage = MockLdapStorage.new
74
+ storage.ldap.user = alice
75
+ user = storage.authenticate(ALICE, 'secr3t')
76
+ refute_nil user
77
+ assert_equal ALICE, user.jid.to_s
78
+ assert_equal 0, storage.authenticate_calls
79
+ assert_equal 1, storage.find_user_calls
80
+ assert_equal 1, storage.save_user_calls
81
+ assert_equal [ALICE, 'secr3t'], storage.ldap.auth.first
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,130 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'test_helper'
4
+
5
+ describe Vines::Store do
6
+ before do
7
+ dir = 'conf/certs'
8
+
9
+ domain, key = certificate('wonderland.lit')
10
+ File.open("#{dir}/wonderland.lit.crt", 'w') {|f| f.write(domain) }
11
+ File.open("#{dir}/wonderland.lit.key", 'w') {|f| f.write(key) }
12
+
13
+ wildcard, key = certificate('*.wonderland.lit')
14
+ File.open("#{dir}/wildcard.lit.crt", 'w') {|f| f.write(wildcard) }
15
+ File.open("#{dir}/wildcard.lit.key", 'w') {|f| f.write(key) }
16
+
17
+ @store = Vines::Store.new('conf/certs')
18
+ end
19
+
20
+ after do
21
+ %w[wonderland.lit.crt wonderland.lit.key wildcard.lit.crt wildcard.lit.key].each do |f|
22
+ name = "conf/certs/#{f}"
23
+ File.delete(name) if File.exists?(name)
24
+ end
25
+ end
26
+
27
+ it 'parses certificate files' do
28
+ refute @store.certs.empty?
29
+ assert_equal OpenSSL::X509::Certificate, @store.certs.first.class
30
+ end
31
+
32
+ it 'ignores expired certificates' do
33
+ assert @store.certs.all? {|c| c.not_after > Time.new }
34
+ end
35
+
36
+ describe 'files_for_domain' do
37
+ it 'handles invalid input' do
38
+ assert_nil @store.files_for_domain(nil)
39
+ assert_nil @store.files_for_domain('')
40
+ end
41
+
42
+ it 'finds files by name' do
43
+ refute_nil @store.files_for_domain('wonderland.lit')
44
+ cert, key = @store.files_for_domain('wonderland.lit')
45
+ assert_certificate_matches_key cert, key
46
+ assert_equal 'wonderland.lit.crt', File.basename(cert)
47
+ assert_equal 'wonderland.lit.key', File.basename(key)
48
+ end
49
+
50
+ it 'finds files for wildcard' do
51
+ refute_nil @store.files_for_domain('foo.wonderland.lit')
52
+ cert, key = @store.files_for_domain('foo.wonderland.lit')
53
+ assert_certificate_matches_key cert, key
54
+ assert_equal 'wildcard.lit.crt', File.basename(cert)
55
+ assert_equal 'wildcard.lit.key', File.basename(key)
56
+ end
57
+ end
58
+
59
+ describe 'domain?' do
60
+ it 'handles invalid input' do
61
+ cert, key = certificate('wonderland.lit')
62
+ refute @store.domain?(nil, nil)
63
+ refute @store.domain?(cert, nil)
64
+ refute @store.domain?(cert, '')
65
+ refute @store.domain?(nil, '')
66
+ assert @store.domain?(cert, 'wonderland.lit')
67
+ end
68
+
69
+ it 'verifies certificate subject domains' do
70
+ cert, key = certificate('wonderland.lit')
71
+ refute @store.domain?(cert, 'bogus')
72
+ refute @store.domain?(cert, 'www.wonderland.lit')
73
+ assert @store.domain?(cert, 'wonderland.lit')
74
+ end
75
+
76
+ it 'verifies certificate subject alt domains' do
77
+ cert, key = certificate('wonderland.lit', 'www.wonderland.lit')
78
+ refute @store.domain?(cert, 'bogus')
79
+ refute @store.domain?(cert, 'tea.wonderland.lit')
80
+ assert @store.domain?(cert, 'www.wonderland.lit')
81
+ assert @store.domain?(cert, 'wonderland.lit')
82
+ end
83
+
84
+ it 'verifies certificate wildcard domains' do
85
+ cert, key = certificate('wonderland.lit', '*.wonderland.lit')
86
+ refute @store.domain?(cert, 'bogus')
87
+ refute @store.domain?(cert, 'one.two.wonderland.lit')
88
+ assert @store.domain?(cert, 'tea.wonderland.lit')
89
+ assert @store.domain?(cert, 'www.wonderland.lit')
90
+ assert @store.domain?(cert, 'wonderland.lit')
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def assert_certificate_matches_key(cert, key)
97
+ refute_nil cert
98
+ refute_nil key
99
+ cert = OpenSSL::X509::Certificate.new(File.read(cert))
100
+ key = OpenSSL::PKey::RSA.new(File.read(key))
101
+ assert_equal cert.public_key.to_s, key.public_key.to_s
102
+ end
103
+
104
+ def certificate(domain, altname=nil)
105
+ # use small key so tests are fast
106
+ key = OpenSSL::PKey::RSA.generate(256)
107
+
108
+ name = OpenSSL::X509::Name.parse("/C=US/ST=Colorado/L=Denver/O=Test/CN=#{domain}")
109
+ cert = OpenSSL::X509::Certificate.new
110
+ cert.version = 2
111
+ cert.subject = name
112
+ cert.issuer = name
113
+ cert.serial = Time.now.to_i
114
+ cert.public_key = key.public_key
115
+ cert.not_before = Time.now
116
+ cert.not_after = Time.now + 3600
117
+
118
+ if altname
119
+ factory = OpenSSL::X509::ExtensionFactory.new
120
+ factory.subject_certificate = cert
121
+ factory.issuer_certificate = cert
122
+ cert.extensions = [
123
+ %w[subjectKeyIdentifier hash],
124
+ %w[subjectAltName] << [domain, altname].map {|n| "DNS:#{n}" }.join(',')
125
+ ].map {|k, v| factory.create_ext(k, v) }
126
+ end
127
+
128
+ [cert.to_pem, key.to_pem]
129
+ end
130
+ end
@@ -0,0 +1,137 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'test_helper'
4
+
5
+ describe Vines::Stream::Client::Auth do
6
+ # disable logging for tests
7
+ Class.new.extend(Vines::Log).log.level = Logger::FATAL
8
+
9
+ class MockStorage < Vines::Storage
10
+ def initialize(raise_error=false)
11
+ @raise_error = raise_error
12
+ end
13
+
14
+ def authenticate(username, password)
15
+ username = username.to_s
16
+ raise 'temp auth fail' if @raise_error
17
+ user = Vines::User.new(jid: 'alice@wonderland.lit')
18
+ users = {'alice@wonderland.lit' => 'secr3t'}
19
+ (users.key?(username) && (users[username] == password)) ? user : nil
20
+ end
21
+
22
+ def find_user(jid)
23
+ end
24
+
25
+ def save_user(user)
26
+ end
27
+ end
28
+
29
+ subject { Vines::Stream::Client::Auth.new(stream) }
30
+ let(:stream) { MiniTest::Mock.new }
31
+
32
+ describe 'error handling' do
33
+ it 'rejects invalid element' do
34
+ node = node('<bogus/>')
35
+ -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
36
+ end
37
+
38
+ it 'rejects invalid element in sasl namespace' do
39
+ node = node(%Q{<bogus xmlns="#{Vines::NAMESPACES[:sasl]}"/>})
40
+ -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
41
+ end
42
+
43
+ it 'rejects auth elements missing sasl namespace' do
44
+ node = node('<auth/>')
45
+ -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
46
+ end
47
+
48
+ it 'rejects auth element with invalid namespace' do
49
+ node = node('<auth xmlns="bogus"/>')
50
+ -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
51
+ end
52
+
53
+ it 'rejects valid auth element missing mechanism' do
54
+ stream.expect :error, nil, [Vines::SaslErrors::InvalidMechanism]
55
+ stream.expect :authentication_mechanisms, ['PLAIN']
56
+ node = node(%Q{<auth xmlns="#{Vines::NAMESPACES[:sasl]}">tokens</auth>})
57
+ subject.node(node)
58
+ stream.verify
59
+ end
60
+
61
+ it 'rejects valid auth element with invalid mechanism' do
62
+ stream.expect :error, nil, [Vines::SaslErrors::InvalidMechanism]
63
+ stream.expect :authentication_mechanisms, ['PLAIN']
64
+ node = node(%Q{<auth xmlns="#{Vines::NAMESPACES[:sasl]}" mechanism="bogus">tokens</auth>})
65
+ subject.node(node)
66
+ stream.verify
67
+ end
68
+ end
69
+
70
+ describe 'plain auth' do
71
+ it 'rejects valid mechanism missing base64 text' do
72
+ stream.expect :error, nil, [Vines::SaslErrors::MalformedRequest]
73
+ node = plain('')
74
+ subject.node(node)
75
+ stream.verify
76
+ end
77
+
78
+ it 'rejects invalid base64 text' do
79
+ stream.expect :error, nil, [Vines::SaslErrors::IncorrectEncoding]
80
+ stream.expect :authentication_mechanisms, ['PLAIN']
81
+ node = plain('tokens')
82
+ subject.node(node)
83
+ stream.verify
84
+ end
85
+
86
+ it 'rejects invalid password' do
87
+ stream.expect :storage, MockStorage.new
88
+ stream.expect :domain, 'wonderland.lit'
89
+ stream.expect :error, nil, [Vines::SaslErrors::NotAuthorized]
90
+ stream.expect :authentication_mechanisms, ['PLAIN']
91
+ node = plain(Base64.strict_encode64("\x00alice\x00bogus"))
92
+ subject.node(node)
93
+ stream.verify
94
+ end
95
+
96
+ it 'passes with valid password' do
97
+ user = Vines::User.new(jid: 'alice@wonderland.lit')
98
+ stream.expect :reset, nil
99
+ stream.expect :domain, 'wonderland.lit'
100
+ stream.expect :storage, MockStorage.new
101
+ stream.expect :user=, nil, [user]
102
+ stream.expect :write, nil, [%Q{<success xmlns="#{Vines::NAMESPACES[:sasl]}"/>}]
103
+ stream.expect :advance, nil, [Vines::Stream::Client::BindRestart]
104
+ stream.expect :authentication_mechanisms, ['PLAIN']
105
+ node = plain(Base64.strict_encode64("\x00alice\x00secr3t"))
106
+ subject.node(node)
107
+ stream.verify
108
+ end
109
+
110
+ it 'raises policy-violation after max auth attempts is reached' do
111
+ stream.expect :domain, 'wonderland.lit'
112
+ stream.expect :storage, MockStorage.new
113
+ node = -> { plain(Base64.strict_encode64("\x00alice\x00bogus")) }
114
+
115
+ stream.expect :authentication_mechanisms, ['PLAIN']
116
+ stream.expect :error, nil, [Vines::SaslErrors::NotAuthorized]
117
+ subject.node(node.call)
118
+ stream.verify
119
+
120
+ stream.expect :authentication_mechanisms, ['PLAIN']
121
+ stream.expect :error, nil, [Vines::SaslErrors::NotAuthorized]
122
+ subject.node(node.call)
123
+ stream.verify
124
+
125
+ stream.expect :authentication_mechanisms, ['PLAIN']
126
+ stream.expect :error, nil, [Vines::StreamErrors::PolicyViolation]
127
+ subject.node(node.call)
128
+ stream.verify
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ def plain(authzid)
135
+ node(%Q{<auth xmlns="#{Vines::NAMESPACES[:sasl]}" mechanism="PLAIN">#{authzid}</auth>})
136
+ end
137
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'test_helper'
4
+
5
+ describe Vines::Stream::Client::Ready do
6
+ STANZAS = []
7
+
8
+ before do
9
+ @stream = MiniTest::Mock.new
10
+ @state = Vines::Stream::Client::Ready.new(@stream, nil)
11
+ def @state.to_stanza(node)
12
+ if node.name == 'bogus'
13
+ nil
14
+ else
15
+ stanza = MiniTest::Mock.new
16
+ stanza.expect(:process, nil)
17
+ stanza.expect(:validate_to, nil)
18
+ stanza.expect(:validate_from, nil)
19
+ STANZAS << stanza
20
+ stanza
21
+ end
22
+ end
23
+ end
24
+
25
+ after do
26
+ STANZAS.clear
27
+ end
28
+
29
+ it 'processes a valid node' do
30
+ node = node('<message/>')
31
+ @state.node(node)
32
+ assert_equal 1, STANZAS.size
33
+ assert STANZAS.map {|s| s.verify }.all?
34
+ end
35
+
36
+ it 'raises an unsupported-stanza-type stream error for invalid node' do
37
+ node = node('<bogus/>')
38
+ assert_raises(Vines::StreamErrors::UnsupportedStanzaType) { @state.node(node) }
39
+ assert STANZAS.empty?
40
+ end
41
+
42
+ private
43
+
44
+ def node(xml)
45
+ Nokogiri::XML(xml).root
46
+ end
47
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'test_helper'
4
+
5
+ describe Vines::Stream::Client::Session do
6
+ subject { Vines::Stream::Client::Session.new(stream) }
7
+ let(:another) { Vines::Stream::Client::Session.new(stream) }
8
+ let(:stream) { OpenStruct.new(config: nil) }
9
+
10
+ describe 'session equality checks' do
11
+ it 'uses class in equality check' do
12
+ (subject <=> 42).must_be_nil
13
+ end
14
+
15
+ it 'is equal to itself' do
16
+ assert subject == subject
17
+ assert subject.eql?(subject)
18
+ assert subject.hash == subject.hash
19
+ end
20
+
21
+ it 'is not equal to another session' do
22
+ refute subject == another
23
+ refute subject.eql?(another)
24
+ refute subject.hash == another.hash
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,52 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'test_helper'
4
+
5
+ describe Vines::Stream::Component::Handshake do
6
+ subject { Vines::Stream::Component::Handshake.new(stream) }
7
+ let(:stream) { MiniTest::Mock.new }
8
+
9
+ describe 'when invalid element is received' do
10
+ it 'raises a not-authorized stream error' do
11
+ node = node('<message/>')
12
+ -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
13
+ end
14
+ end
15
+
16
+ describe 'when handshake with no text is received' do
17
+ it 'raises a not-authorized stream error' do
18
+ stream.expect :secret, 'secr3t'
19
+ node = node('<handshake/>')
20
+ -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
21
+ stream.verify
22
+ end
23
+ end
24
+
25
+ describe 'when handshake with invalid secret is received' do
26
+ it 'raises a not-authorized stream error' do
27
+ stream.expect :secret, 'secr3t'
28
+ node = node('<handshake>bogus</handshake>')
29
+ -> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
30
+ stream.verify
31
+ end
32
+ end
33
+
34
+ describe 'when good handshake is received' do
35
+ let(:router) { MiniTest::Mock.new }
36
+
37
+ before do
38
+ router.expect :<<, nil, [stream]
39
+ stream.expect :router, router
40
+ stream.expect :secret, 'secr3t'
41
+ stream.expect :write, nil, ['<handshake/>']
42
+ stream.expect :advance, nil, [Vines::Stream::Component::Ready.new(stream)]
43
+ end
44
+
45
+ it 'completes the handshake and advances the stream into the ready state' do
46
+ node = node('<handshake>secr3t</handshake>')
47
+ subject.node(node)
48
+ stream.verify
49
+ router.verify
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,103 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'test_helper'
4
+
5
+ describe Vines::Stream::Component::Ready do
6
+ subject { Vines::Stream::Component::Ready.new(stream, nil) }
7
+ let(:alice) { Vines::User.new(jid: 'alice@tea.wonderland.lit') }
8
+ let(:hatter) { Vines::User.new(jid: 'hatter@wonderland.lit') }
9
+ let(:stream) { MiniTest::Mock.new }
10
+ let(:config) do
11
+ Vines::Config.new do
12
+ host 'wonderland.lit' do
13
+ storage(:fs) { dir Dir.tmpdir }
14
+ end
15
+ end
16
+ end
17
+
18
+ before do
19
+ class << stream
20
+ attr_accessor :config
21
+ end
22
+ stream.config = config
23
+ end
24
+
25
+ describe 'when missing to and from addresses' do
26
+ it 'raises an improper-addressing stream error' do
27
+ node = node('<message/>')
28
+ -> { subject.node(node) }.must_raise Vines::StreamErrors::ImproperAddressing
29
+ stream.verify
30
+ end
31
+ end
32
+
33
+ describe 'when missing from address' do
34
+ it 'raises an improper-addressing stream error' do
35
+ node = node(%q{<message to="hatter@wonderland.lit"/>})
36
+ -> { subject.node(node) }.must_raise Vines::StreamErrors::ImproperAddressing
37
+ stream.verify
38
+ end
39
+ end
40
+
41
+ describe 'when missing to address' do
42
+ it 'raises an improper-addressing stream error' do
43
+ node = node(%q{<message from="alice@tea.wonderland.lit"/>})
44
+ -> { subject.node(node) }.must_raise Vines::StreamErrors::ImproperAddressing
45
+ stream.verify
46
+ end
47
+ end
48
+
49
+ describe 'when from address domain does not match component domain' do
50
+ it 'raises and invalid-from stream error' do
51
+ stream.expect :remote_domain, 'tea.wonderland.lit'
52
+ node = node(%q{<message from="alice@bogus.wonderland.lit" to="hatter@wonderland.lit"/>})
53
+ -> { subject.node(node) }.must_raise Vines::StreamErrors::InvalidFrom
54
+ stream.verify
55
+ end
56
+ end
57
+
58
+ describe 'when unrecognized element is received' do
59
+ it 'raises an unsupported-stanza-type stream error' do
60
+ node = node('<bogus/>')
61
+ -> { subject.node(node) }.must_raise Vines::StreamErrors::UnsupportedStanzaType
62
+ stream.verify
63
+ end
64
+ end
65
+
66
+ describe 'when addressed to a remote jid' do
67
+ let(:router) { MiniTest::Mock.new }
68
+ let(:xml) { node(%q{<message from="alice@tea.wonderland.lit" to="romeo@verona.lit"/>}) }
69
+
70
+ before do
71
+ router.expect :route, nil, [xml]
72
+ stream.expect :remote_domain, 'tea.wonderland.lit'
73
+ stream.expect :user=, nil, [alice]
74
+ stream.expect :router, router
75
+ end
76
+
77
+ it 'routes rather than handle locally' do
78
+ subject.node(xml)
79
+ stream.verify
80
+ router.verify
81
+ end
82
+ end
83
+
84
+ describe 'when addressed to a local jid' do
85
+ let(:recipient) { MiniTest::Mock.new }
86
+ let(:xml) { node(%q{<message from="alice@tea.wonderland.lit" to="hatter@wonderland.lit"/>}) }
87
+
88
+ before do
89
+ recipient.expect :user, hatter
90
+ recipient.expect :write, nil, [xml]
91
+ stream.expect :remote_domain, 'tea.wonderland.lit'
92
+ stream.expect :user=, nil, [alice]
93
+ stream.expect :user, alice
94
+ stream.expect :connected_resources, [recipient], [hatter.jid]
95
+ end
96
+
97
+ it 'sends the message to the connected stream' do
98
+ subject.node(xml)
99
+ stream.verify
100
+ recipient.verify
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'test_helper'
4
+
5
+ describe Vines::Stream::Component::Start do
6
+ before do
7
+ @stream = MiniTest::Mock.new
8
+ @state = Vines::Stream::Component::Start.new(@stream)
9
+ end
10
+
11
+ it 'raises not-authorized stream error for invalid element' do
12
+ node = node('<message/>')
13
+ assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
14
+ end
15
+
16
+ it 'raises not-authorized stream error for missing stream namespace' do
17
+ node = node('<stream:stream/>')
18
+ assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
19
+ end
20
+
21
+ it 'raises not-authorized stream error for invalid stream namespace' do
22
+ node = node('<stream:stream xmlns="bogus"/>')
23
+ assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
24
+ end
25
+
26
+ it 'advances the state machine for valid stream header' do
27
+ node = node(%q{<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:component:accept" to="tea.wonderland.lit"/>})
28
+ @stream.expect(:start, nil, [node])
29
+ @stream.expect(:advance, nil, [Vines::Stream::Component::Handshake.new(@stream)])
30
+ @state.node(node)
31
+ assert @stream.verify
32
+ end
33
+
34
+ private
35
+
36
+ def node(xml)
37
+ Nokogiri::XML(xml).root
38
+ end
39
+ end
@@ -0,0 +1,70 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'test_helper'
4
+
5
+ describe Vines::Stream::Http::Auth do
6
+ before do
7
+ @stream = MiniTest::Mock.new
8
+ @state = Vines::Stream::Http::Auth.new(@stream, nil)
9
+ end
10
+
11
+ def test_missing_body_raises_error
12
+ node = node('<presence type="unavailable"/>')
13
+ @stream.expect(:valid_session?, true, [nil])
14
+ assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
15
+ end
16
+
17
+ def test_body_with_missing_namespace_raises_error
18
+ node = node('<body rid="42" sid="12"/>')
19
+ @stream.expect(:valid_session?, true, ['12'])
20
+ assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
21
+ end
22
+
23
+ def test_missing_rid_raises_error
24
+ node = node('<body xmlns="http://jabber.org/protocol/httpbind" sid="12"/>')
25
+ @stream.expect(:valid_session?, true, ['12'])
26
+ assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
27
+ end
28
+
29
+ def test_invalid_session_raises_error
30
+ @stream.expect(:valid_session?, false, ['12'])
31
+ node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12"/>')
32
+ assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
33
+ assert @stream.verify
34
+ end
35
+
36
+ def test_empty_body_raises_error
37
+ node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12"/>')
38
+ @stream.expect(:valid_session?, true, ['12'])
39
+ @stream.expect(:parse_body, [], [node])
40
+ assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
41
+ assert @stream.verify
42
+ end
43
+
44
+ def test_body_with_two_children_raises_error
45
+ node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12"><message/><message/></body>')
46
+ message = node('<message/>')
47
+ @stream.expect(:valid_session?, true, ['12'])
48
+ @stream.expect(:parse_body, [message, message], [node])
49
+ assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
50
+ assert @stream.verify
51
+ end
52
+
53
+ def test_valid_body_processes
54
+ auth = node(%Q{<auth xmlns="#{Vines::NAMESPACES[:sasl]}" mechanism="PLAIN"/>})
55
+ node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12"></body>')
56
+ node << auth
57
+ @stream.expect(:valid_session?, true, ['12'])
58
+ @stream.expect(:parse_body, [auth], [node])
59
+ # this error means we correctly called the parent method Client#node
60
+ @stream.expect(:error, nil, [Vines::SaslErrors::MalformedRequest.new])
61
+ @state.node(node)
62
+ assert @stream.verify
63
+ end
64
+
65
+ private
66
+
67
+ def node(xml)
68
+ Nokogiri::XML(xml).root
69
+ end
70
+ end