diaspora-vines 0.1.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.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +7 -0
- data/Rakefile +23 -0
- data/bin/vines +4 -0
- data/conf/certs/README +39 -0
- data/conf/certs/ca-bundle.crt +3895 -0
- data/conf/config.rb +42 -0
- data/lib/vines/cli.rb +132 -0
- data/lib/vines/cluster/connection.rb +26 -0
- data/lib/vines/cluster/publisher.rb +55 -0
- data/lib/vines/cluster/pubsub.rb +92 -0
- data/lib/vines/cluster/sessions.rb +125 -0
- data/lib/vines/cluster/subscriber.rb +108 -0
- data/lib/vines/cluster.rb +246 -0
- data/lib/vines/command/bcrypt.rb +12 -0
- data/lib/vines/command/cert.rb +50 -0
- data/lib/vines/command/init.rb +68 -0
- data/lib/vines/command/ldap.rb +38 -0
- data/lib/vines/command/restart.rb +12 -0
- data/lib/vines/command/schema.rb +24 -0
- data/lib/vines/command/start.rb +28 -0
- data/lib/vines/command/stop.rb +18 -0
- data/lib/vines/config/host.rb +125 -0
- data/lib/vines/config/port.rb +132 -0
- data/lib/vines/config/pubsub.rb +108 -0
- data/lib/vines/config.rb +223 -0
- data/lib/vines/contact.rb +111 -0
- data/lib/vines/daemon.rb +78 -0
- data/lib/vines/error.rb +150 -0
- data/lib/vines/jid.rb +95 -0
- data/lib/vines/kit.rb +23 -0
- data/lib/vines/log.rb +24 -0
- data/lib/vines/router.rb +179 -0
- data/lib/vines/stanza/iq/auth.rb +18 -0
- data/lib/vines/stanza/iq/disco_info.rb +45 -0
- data/lib/vines/stanza/iq/disco_items.rb +29 -0
- data/lib/vines/stanza/iq/error.rb +16 -0
- data/lib/vines/stanza/iq/ping.rb +16 -0
- data/lib/vines/stanza/iq/private_storage.rb +83 -0
- data/lib/vines/stanza/iq/query.rb +10 -0
- data/lib/vines/stanza/iq/result.rb +16 -0
- data/lib/vines/stanza/iq/roster.rb +140 -0
- data/lib/vines/stanza/iq/session.rb +17 -0
- data/lib/vines/stanza/iq/vcard.rb +56 -0
- data/lib/vines/stanza/iq/version.rb +25 -0
- data/lib/vines/stanza/iq.rb +48 -0
- data/lib/vines/stanza/message.rb +40 -0
- data/lib/vines/stanza/presence/error.rb +23 -0
- data/lib/vines/stanza/presence/probe.rb +37 -0
- data/lib/vines/stanza/presence/subscribe.rb +42 -0
- data/lib/vines/stanza/presence/subscribed.rb +51 -0
- data/lib/vines/stanza/presence/unavailable.rb +15 -0
- data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
- data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
- data/lib/vines/stanza/presence.rb +141 -0
- data/lib/vines/stanza/pubsub/create.rb +39 -0
- data/lib/vines/stanza/pubsub/delete.rb +41 -0
- data/lib/vines/stanza/pubsub/publish.rb +66 -0
- data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
- data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
- data/lib/vines/stanza/pubsub.rb +22 -0
- data/lib/vines/stanza.rb +175 -0
- data/lib/vines/storage/ldap.rb +71 -0
- data/lib/vines/storage/local.rb +139 -0
- data/lib/vines/storage/null.rb +39 -0
- data/lib/vines/storage/sql.rb +138 -0
- data/lib/vines/storage.rb +239 -0
- data/lib/vines/store.rb +110 -0
- data/lib/vines/stream/client/auth.rb +74 -0
- data/lib/vines/stream/client/auth_restart.rb +29 -0
- data/lib/vines/stream/client/bind.rb +72 -0
- data/lib/vines/stream/client/bind_restart.rb +24 -0
- data/lib/vines/stream/client/closed.rb +13 -0
- data/lib/vines/stream/client/ready.rb +17 -0
- data/lib/vines/stream/client/session.rb +210 -0
- data/lib/vines/stream/client/start.rb +27 -0
- data/lib/vines/stream/client/tls.rb +38 -0
- data/lib/vines/stream/client.rb +84 -0
- data/lib/vines/stream/component/handshake.rb +26 -0
- data/lib/vines/stream/component/ready.rb +23 -0
- data/lib/vines/stream/component/start.rb +19 -0
- data/lib/vines/stream/component.rb +58 -0
- data/lib/vines/stream/http/auth.rb +22 -0
- data/lib/vines/stream/http/bind.rb +32 -0
- data/lib/vines/stream/http/bind_restart.rb +37 -0
- data/lib/vines/stream/http/ready.rb +29 -0
- data/lib/vines/stream/http/request.rb +172 -0
- data/lib/vines/stream/http/session.rb +120 -0
- data/lib/vines/stream/http/sessions.rb +65 -0
- data/lib/vines/stream/http/start.rb +23 -0
- data/lib/vines/stream/http.rb +157 -0
- data/lib/vines/stream/parser.rb +79 -0
- data/lib/vines/stream/sasl.rb +128 -0
- data/lib/vines/stream/server/auth.rb +13 -0
- data/lib/vines/stream/server/auth_restart.rb +13 -0
- data/lib/vines/stream/server/final_restart.rb +21 -0
- data/lib/vines/stream/server/outbound/auth.rb +31 -0
- data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
- data/lib/vines/stream/server/outbound/final_features.rb +28 -0
- data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/start.rb +20 -0
- data/lib/vines/stream/server/outbound/tls.rb +30 -0
- data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
- data/lib/vines/stream/server/ready.rb +24 -0
- data/lib/vines/stream/server/start.rb +13 -0
- data/lib/vines/stream/server/tls.rb +13 -0
- data/lib/vines/stream/server.rb +150 -0
- data/lib/vines/stream/state.rb +60 -0
- data/lib/vines/stream.rb +247 -0
- data/lib/vines/token_bucket.rb +55 -0
- data/lib/vines/user.rb +123 -0
- data/lib/vines/version.rb +6 -0
- data/lib/vines/xmpp_server.rb +25 -0
- data/lib/vines.rb +203 -0
- data/test/cluster/publisher_test.rb +57 -0
- data/test/cluster/sessions_test.rb +47 -0
- data/test/cluster/subscriber_test.rb +109 -0
- data/test/config/host_test.rb +369 -0
- data/test/config/pubsub_test.rb +187 -0
- data/test/config_test.rb +732 -0
- data/test/contact_test.rb +102 -0
- data/test/error_test.rb +58 -0
- data/test/ext/nokogiri.rb +14 -0
- data/test/jid_test.rb +147 -0
- data/test/kit_test.rb +31 -0
- data/test/router_test.rb +243 -0
- data/test/stanza/iq/disco_info_test.rb +78 -0
- data/test/stanza/iq/disco_items_test.rb +49 -0
- data/test/stanza/iq/private_storage_test.rb +184 -0
- data/test/stanza/iq/roster_test.rb +229 -0
- data/test/stanza/iq/session_test.rb +25 -0
- data/test/stanza/iq/vcard_test.rb +146 -0
- data/test/stanza/iq/version_test.rb +64 -0
- data/test/stanza/iq_test.rb +70 -0
- data/test/stanza/message_test.rb +126 -0
- data/test/stanza/presence/probe_test.rb +50 -0
- data/test/stanza/presence/subscribe_test.rb +83 -0
- data/test/stanza/pubsub/create_test.rb +116 -0
- data/test/stanza/pubsub/delete_test.rb +169 -0
- data/test/stanza/pubsub/publish_test.rb +309 -0
- data/test/stanza/pubsub/subscribe_test.rb +205 -0
- data/test/stanza/pubsub/unsubscribe_test.rb +148 -0
- data/test/stanza_test.rb +85 -0
- data/test/storage/ldap_test.rb +201 -0
- data/test/storage/local_test.rb +59 -0
- data/test/storage/mock_redis.rb +97 -0
- data/test/storage/null_test.rb +29 -0
- data/test/storage/storage_tests.rb +182 -0
- data/test/storage_test.rb +85 -0
- data/test/store_test.rb +130 -0
- data/test/stream/client/auth_test.rb +137 -0
- data/test/stream/client/ready_test.rb +47 -0
- data/test/stream/client/session_test.rb +27 -0
- data/test/stream/component/handshake_test.rb +52 -0
- data/test/stream/component/ready_test.rb +103 -0
- data/test/stream/component/start_test.rb +39 -0
- data/test/stream/http/auth_test.rb +70 -0
- data/test/stream/http/ready_test.rb +86 -0
- data/test/stream/http/request_test.rb +209 -0
- data/test/stream/http/sessions_test.rb +49 -0
- data/test/stream/http/start_test.rb +50 -0
- data/test/stream/parser_test.rb +122 -0
- data/test/stream/sasl_test.rb +195 -0
- data/test/stream/server/auth_test.rb +61 -0
- data/test/stream/server/outbound/auth_test.rb +75 -0
- data/test/stream/server/ready_test.rb +98 -0
- data/test/test_helper.rb +42 -0
- data/test/token_bucket_test.rb +44 -0
- data/test/user_test.rb +96 -0
- data/vines.gemspec +30 -0
- 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
|
data/test/store_test.rb
ADDED
@@ -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
|