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,54 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'storage_tests'
4
+ require 'vines'
5
+ require 'test/unit'
6
+
7
+ class LocalTest < Test::Unit::TestCase
8
+ include StorageTests
9
+
10
+ def setup
11
+ @files = {
12
+ :empty => './empty@wonderland.lit.user',
13
+ :no_pass => './no_password@wonderland.lit.user',
14
+ :clear_pass => './clear_password@wonderland.lit.user',
15
+ :bcrypt => './bcrypt_password@wonderland.lit.user',
16
+ :full => './full@wonderland.lit.user',
17
+ :vcard => './full@wonderland.lit.vcard'
18
+ }
19
+ File.open(@files[:empty], 'w') {|f| f.write('') }
20
+ File.open(@files[:no_pass], 'w') {|f| f.write('foo: bar') }
21
+ File.open(@files[:clear_pass], 'w') {|f| f.write('password: secret') }
22
+ File.open(@files[:bcrypt], 'w') {|f| f.write("password: #{BCrypt::Password.create('secret')}") }
23
+ File.open(@files[:full], 'w') do |f|
24
+ f.puts("password: #{BCrypt::Password.create('secret')}")
25
+ f.puts("name: Tester")
26
+ f.puts("roster:")
27
+ f.puts(" contact1@wonderland.lit:")
28
+ f.puts(" name: Contact1")
29
+ f.puts(" groups: [Group1, Group2]")
30
+ f.puts(" contact2@wonderland.lit:")
31
+ f.puts(" name: Contact2")
32
+ f.puts(" groups: [Group3, Group4]")
33
+ end
34
+ File.open(@files[:vcard], 'w') {|f| f.write(StorageTests::VCARD.to_xml) }
35
+ end
36
+
37
+ def teardown
38
+ misc = %w[user vcard].map {|ext| "./save_user@domain.tld.#{ext}" }
39
+ [*misc, *@files.values].each do |f|
40
+ File.delete(f) if File.exist?(f)
41
+ end
42
+ end
43
+
44
+ def storage
45
+ Vines::Storage::Local.new { dir '.' }
46
+ end
47
+
48
+ def test_init
49
+ assert_raise(RuntimeError) { Vines::Storage::Local.new {} }
50
+ assert_raise(RuntimeError) { Vines::Storage::Local.new { dir 'bogus' } }
51
+ assert_raise(RuntimeError) { Vines::Storage::Local.new { dir '/sbin' } }
52
+ assert_nothing_raised { Vines::Storage::Local.new { dir '.' } }
53
+ end
54
+ end
@@ -0,0 +1,75 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'storage_tests'
4
+ require 'vines'
5
+ require 'test/unit'
6
+
7
+ class RedisTest < Test::Unit::TestCase
8
+ include StorageTests
9
+
10
+ MOCK_REDIS = Class.new do
11
+ def initialize
12
+ @db = {}
13
+ end
14
+ def get(key)
15
+ EM.next_tick { yield @db[key] }
16
+ end
17
+ def hgetall(key)
18
+ EM.next_tick { yield @db[key] || {} }
19
+ end
20
+ def set(key, value)
21
+ @db[key] = value
22
+ EM.next_tick { yield if block_given? }
23
+ end
24
+ def hmset(key, *args)
25
+ @db[key] = Hash[*args]
26
+ EM.next_tick { yield if block_given? }
27
+ end
28
+ def del(key)
29
+ @db.delete(key)
30
+ EM.next_tick { yield if block_given? }
31
+ end
32
+ def flushdb
33
+ @db.clear
34
+ EM.next_tick { yield if block_given? }
35
+ end
36
+ end.new
37
+
38
+ def setup
39
+ EMLoop.new do
40
+ db = MOCK_REDIS
41
+ db.set('user:empty@wonderland.lit', {}.to_json)
42
+ db.set('user:no_password@wonderland.lit', {'foo' => 'bar'}.to_json)
43
+ db.set('user:clear_password@wonderland.lit', {'password' => 'secret'}.to_json)
44
+ db.set('user:bcrypt_password@wonderland.lit', {'password' => BCrypt::Password.create('secret')}.to_json)
45
+ db.set('user:full@wonderland.lit', {
46
+ 'password' => BCrypt::Password.create('secret'),
47
+ 'name' => 'Tester'
48
+ }.to_json)
49
+ db.hmset('roster:full@wonderland.lit',
50
+ 'contact1@wonderland.lit',
51
+ {'name' => 'Contact1', 'groups' => %w[Group1 Group2]}.to_json,
52
+ 'contact2@wonderland.lit',
53
+ {'name' => 'Contact2', 'groups' => %w[Group3 Group4]}.to_json)
54
+ db.set('vcard:full@wonderland.lit', {'card' => StorageTests::VCARD.to_xml}.to_json)
55
+ end
56
+ end
57
+
58
+ def teardown
59
+ MOCK_REDIS.flushdb
60
+ end
61
+
62
+ def storage
63
+ storage = Vines::Storage::Redis.new { host 'localhost'; port 6397 }
64
+ def storage.redis; RedisTest::MOCK_REDIS; end
65
+ storage
66
+ end
67
+
68
+ def test_init
69
+ EMLoop.new do
70
+ assert_nothing_raised { Vines::Storage::Redis.new {} }
71
+ assert_nothing_raised { Vines::Storage::Redis.new { host 'localhost' } }
72
+ assert_nothing_raised { Vines::Storage::Redis.new { host'localhost'; port '6379' } }
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'storage_tests'
4
+ require 'vines'
5
+ require 'test/unit'
6
+
7
+ class SqlTest < Test::Unit::TestCase
8
+ include StorageTests
9
+
10
+ DB_FILE = "./xmpp_testcase.db"
11
+ ActiveRecord::Migration.verbose = false
12
+
13
+ def setup
14
+ storage.create_schema(:force => true)
15
+ Vines::Storage::Sql::User.new(:jid => 'empty@wonderland.lit', :name => '', :password => '').save
16
+ Vines::Storage::Sql::User.new(:jid => 'no_password@wonderland.lit', :name => '', :password => '').save
17
+ Vines::Storage::Sql::User.new(:jid => 'clear_password@wonderland.lit', :name => '',
18
+ :password => 'secret').save
19
+ Vines::Storage::Sql::User.new(:jid => 'bcrypt_password@wonderland.lit', :name => '',
20
+ :password => BCrypt::Password.create('secret')).save
21
+ groups = %w[Group1 Group2 Group3 Group4].map do |name|
22
+ Vines::Storage::Sql::Group.find_or_create_by_name(name)
23
+ end
24
+ full = Vines::Storage::Sql::User.new(
25
+ :jid => 'full@wonderland.lit',
26
+ :name => 'Tester',
27
+ :password => BCrypt::Password.create('secret'),
28
+ :vcard => StorageTests::VCARD.to_xml)
29
+ full.contacts << Vines::Storage::Sql::Contact.new(
30
+ :jid => 'contact1@wonderland.lit',
31
+ :name => 'Contact1',
32
+ :groups => groups[0, 2],
33
+ :subscription => 'both')
34
+ full.contacts << Vines::Storage::Sql::Contact.new(
35
+ :jid => 'contact2@wonderland.lit',
36
+ :name => 'Contact2',
37
+ :groups => groups[2, 2],
38
+ :subscription => 'both')
39
+ full.save
40
+ end
41
+
42
+ def teardown
43
+ File.delete(DB_FILE) if File.exist?(DB_FILE)
44
+ end
45
+
46
+ def storage
47
+ Vines::Storage::Sql.new { adapter 'sqlite3'; database DB_FILE }
48
+ end
49
+
50
+ def test_init
51
+ assert_raise(RuntimeError) { Vines::Storage::Sql.new {} }
52
+ assert_raise(RuntimeError) { Vines::Storage::Sql.new { adapter 'postgresql' } }
53
+ assert_nothing_raised { Vines::Storage::Sql.new { adapter 'sqlite3'; database ':memory:' } }
54
+ end
55
+ end
@@ -0,0 +1,134 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'vines'
4
+ require 'ext/nokogiri'
5
+ require 'test/unit'
6
+
7
+ # Mixin methods for storage implementation test classes. The behavioral
8
+ # tests are the same regardless of implementation so share those methods
9
+ # here.
10
+ module StorageTests
11
+ VCARD = Nokogiri::XML(%q{
12
+ <vCard xmlns="vcard-temp">
13
+ <FN>Alice in Wonderland</FN>
14
+ </vCard>
15
+ }.strip).root
16
+
17
+ class EMLoop
18
+ def initialize
19
+ EM.run do
20
+ Fiber.new do
21
+ yield
22
+ EM.stop
23
+ end.resume
24
+ end
25
+ end
26
+ end
27
+
28
+ def test_authenticate
29
+ EMLoop.new do
30
+ db = storage
31
+ assert_nil db.authenticate(nil, nil)
32
+ assert_nil db.authenticate(nil, 'secret')
33
+ assert_nil db.authenticate('bogus', nil)
34
+ assert_nil db.authenticate('bogus', 'secret')
35
+ assert_nil db.authenticate('empty@wonderland.lit', 'secret')
36
+ assert_nil db.authenticate('no_password@wonderland.lit', 'secret')
37
+ assert_nil db.authenticate('clear_password@wonderland.lit', 'secret')
38
+
39
+ user = db.authenticate('bcrypt_password@wonderland.lit', 'secret')
40
+ assert_not_nil user
41
+ assert_equal('bcrypt_password@wonderland.lit', user.jid.to_s)
42
+
43
+ user = db.authenticate('full@wonderland.lit', 'secret')
44
+ assert_not_nil user
45
+ assert_equal 'Tester', user.name
46
+ assert_equal 'full@wonderland.lit', user.jid.to_s
47
+
48
+ assert_equal 2, user.roster.length
49
+ assert_equal 'contact1@wonderland.lit', user.roster[0].jid.to_s
50
+ assert_equal 'Contact1', user.roster[0].name
51
+ assert_equal 2, user.roster[0].groups.length
52
+ assert_equal 'Group1', user.roster[0].groups[0]
53
+ assert_equal 'Group2', user.roster[0].groups[1]
54
+
55
+ assert_equal 'contact2@wonderland.lit', user.roster[1].jid.to_s
56
+ assert_equal 'Contact2', user.roster[1].name
57
+ assert_equal 2, user.roster[1].groups.length
58
+ assert_equal 'Group3', user.roster[1].groups[0]
59
+ assert_equal 'Group4', user.roster[1].groups[1]
60
+ end
61
+ end
62
+
63
+ def test_find_user
64
+ EMLoop.new do
65
+ db = storage
66
+ user = db.find_user(nil)
67
+ assert_nil user
68
+
69
+ user = db.find_user('full@wonderland.lit')
70
+ assert_not_nil user
71
+ assert_equal 'full@wonderland.lit', user.jid.to_s
72
+
73
+ user = db.find_user(Vines::JID.new('full@wonderland.lit'))
74
+ assert_not_nil user
75
+ assert_equal 'full@wonderland.lit', user.jid.to_s
76
+
77
+ user = db.find_user(Vines::JID.new('full@wonderland.lit/resource'))
78
+ assert_not_nil user
79
+ assert_equal 'full@wonderland.lit', user.jid.to_s
80
+ end
81
+ end
82
+
83
+ def test_save_user
84
+ EMLoop.new do
85
+ db = storage
86
+ user = Vines::User.new(
87
+ :jid => 'save_user@domain.tld/resource1',
88
+ :name => 'Save User',
89
+ :password => 'secret')
90
+ user.roster << Vines::Contact.new(
91
+ :jid => 'contact1@domain.tld/resource2',
92
+ :name => 'Contact 1')
93
+ db.save_user(user)
94
+ user = db.find_user('save_user@domain.tld')
95
+ assert_not_nil user
96
+ assert_equal 'save_user@domain.tld', user.jid.to_s
97
+ assert_equal 'Save User', user.name
98
+ assert_equal 1, user.roster.length
99
+ assert_equal 'contact1@domain.tld', user.roster[0].jid.to_s
100
+ assert_equal 'Contact 1', user.roster[0].name
101
+ end
102
+ end
103
+
104
+ def test_find_vcard
105
+ EMLoop.new do
106
+ db = storage
107
+ card = db.find_vcard(nil)
108
+ assert_nil card
109
+
110
+ card = db.find_vcard('full@wonderland.lit')
111
+ assert_not_nil card
112
+ assert_equal VCARD, card
113
+
114
+ card = db.find_vcard(Vines::JID.new('full@wonderland.lit'))
115
+ assert_not_nil card
116
+ assert_equal VCARD, card
117
+
118
+ card = db.find_vcard(Vines::JID.new('full@wonderland.lit/resource'))
119
+ assert_not_nil card
120
+ assert_equal VCARD, card
121
+ end
122
+ end
123
+
124
+ def test_save_vcard
125
+ EMLoop.new do
126
+ db = storage
127
+ db.save_user(Vines::User.new(:jid => 'save_user@domain.tld'))
128
+ db.save_vcard('save_user@domain.tld/resource1', VCARD)
129
+ card = db.find_vcard('save_user@domain.tld')
130
+ assert_not_nil card
131
+ assert_equal VCARD, card
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,90 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'storage_tests'
4
+ require 'vines'
5
+ require 'minitest/mock'
6
+ require 'test/unit'
7
+
8
+ class StorageTest < Test::Unit::TestCase
9
+ ALICE = 'alice@wonderland.lit'.freeze
10
+
11
+ class MockLdapStorage < Vines::Storage
12
+ attr_reader :authenticate_calls, :find_user_calls, :save_user_calls
13
+
14
+ def initialize(found_user=nil)
15
+ @found_user = found_user
16
+ @authenticate_calls = @find_user_calls = @save_user_calls = 0
17
+ @ldap = MiniTest::Mock.new
18
+ end
19
+
20
+ def authenticate(username, password)
21
+ @authenticate_calls += 1
22
+ nil
23
+ end
24
+ wrap_ldap :authenticate
25
+
26
+ def find_user(jid)
27
+ @find_user_calls += 1
28
+ @found_user
29
+ end
30
+
31
+ def save_user(user)
32
+ @save_user_calls += 1
33
+ end
34
+ end
35
+
36
+ def test_authenticate_with_ldap_missing_password
37
+ StorageTests::EMLoop.new do
38
+ storage = MockLdapStorage.new
39
+ user = storage.authenticate(ALICE, '')
40
+ assert_nil user
41
+ assert_equal 0, storage.authenticate_calls
42
+ assert_equal 0, storage.find_user_calls
43
+ assert_equal 0, storage.save_user_calls
44
+ assert storage.ldap.verify
45
+ end
46
+ end
47
+
48
+ def test_authenticate_with_ldap_bad_password
49
+ StorageTests::EMLoop.new do
50
+ storage = MockLdapStorage.new
51
+ storage.ldap.expect(:authenticate, nil, [ALICE, 'bogus'])
52
+ user = storage.authenticate(ALICE, 'bogus')
53
+ assert_nil user
54
+ assert_equal 0, storage.authenticate_calls
55
+ assert_equal 0, storage.find_user_calls
56
+ assert_equal 0, storage.save_user_calls
57
+ assert storage.ldap.verify
58
+ end
59
+ end
60
+
61
+ def test_authenticate_with_ldap_user_exists_in_database
62
+ StorageTests::EMLoop.new do
63
+ alice = Vines::User.new(:jid => ALICE)
64
+ storage = MockLdapStorage.new(alice)
65
+ storage.ldap.expect(:authenticate, alice, [ALICE, 'secr3t'])
66
+ user = storage.authenticate(ALICE, 'secr3t')
67
+ assert_not_nil user
68
+ assert_equal ALICE, user.jid.to_s
69
+ assert_equal 0, storage.authenticate_calls
70
+ assert_equal 1, storage.find_user_calls
71
+ assert_equal 0, storage.save_user_calls
72
+ assert storage.ldap.verify
73
+ end
74
+ end
75
+
76
+ def test_authenticate_with_ldap_save_user_to_database
77
+ StorageTests::EMLoop.new do
78
+ alice = Vines::User.new(:jid => ALICE)
79
+ storage = MockLdapStorage.new
80
+ storage.ldap.expect(:authenticate, alice, [ALICE, 'secr3t'])
81
+ user = storage.authenticate(ALICE, 'secr3t')
82
+ assert_not_nil user
83
+ assert_equal ALICE, user.jid.to_s
84
+ assert_equal 0, storage.authenticate_calls
85
+ assert_equal 1, storage.find_user_calls
86
+ assert_equal 1, storage.save_user_calls
87
+ assert storage.ldap.verify
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,127 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'vines'
4
+ require 'minitest/mock'
5
+ require 'test/unit'
6
+
7
+ class ClientAuthTest < Test::Unit::TestCase
8
+ # disable logging for tests
9
+ Class.new.extend(Vines::Log).log.level = Logger::FATAL
10
+
11
+ class MockStorage < Vines::Storage
12
+ def initialize(raise_error=false)
13
+ @raise_error = raise_error
14
+ end
15
+
16
+ def authenticate(username, password)
17
+ raise 'temp auth fail' if @raise_error
18
+ user = Vines::User.new(:jid => 'alice@wonderland.lit')
19
+ users = {'alice@wonderland.lit' => 'secr3t'}
20
+ (users.key?(username) && (users[username] == password)) ? user : nil
21
+ end
22
+
23
+ def find_user(jid)
24
+ end
25
+
26
+ def save_user(user)
27
+ end
28
+ end
29
+
30
+ def setup
31
+ @stream = MiniTest::Mock.new
32
+ @state = Vines::Stream::Client::Auth.new(@stream)
33
+ end
34
+
35
+ def test_invalid_element
36
+ node = node('<bogus/>')
37
+ assert_raise(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
38
+ end
39
+
40
+ def test_invalid_sasl_element
41
+ node = node(%Q{<bogus xmlns="#{Vines::NAMESPACES[:sasl]}"/>})
42
+ assert_raise(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
43
+ end
44
+
45
+ def test_missing_namespace
46
+ node = node('<auth/>')
47
+ assert_raise(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
48
+ end
49
+
50
+ def test_invalid_namespace
51
+ node = node('<auth xmlns="bogus"/>')
52
+ assert_raise(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
53
+ end
54
+
55
+ def test_missing_mechanism
56
+ @stream.expect(:error, nil, [Vines::SaslErrors::InvalidMechanism.new])
57
+ node = node(%Q{<auth xmlns="#{Vines::NAMESPACES[:sasl]}">tokens</auth>})
58
+ assert_nothing_raised { @state.node(node) }
59
+ assert @stream.verify
60
+ end
61
+
62
+ def test_invalid_mechanism
63
+ @stream.expect(:error, nil, [Vines::SaslErrors::InvalidMechanism.new])
64
+ node = node(%Q{<auth xmlns="#{Vines::NAMESPACES[:sasl]}" mechanism="bogus">tokens</auth>})
65
+ assert_nothing_raised { @state.node(node) }
66
+ assert @stream.verify
67
+ end
68
+
69
+ def test_missing_text
70
+ @stream.expect(:error, nil, [Vines::SaslErrors::MalformedRequest.new])
71
+ node = node(%Q{<auth xmlns="#{Vines::NAMESPACES[:sasl]}" mechanism="PLAIN"></auth>})
72
+ assert_nothing_raised { @state.node(node) }
73
+ assert @stream.verify
74
+ end
75
+
76
+ def test_plain_auth_storage_error
77
+ @stream.expect(:storage, MockStorage.new(true))
78
+ @stream.expect(:error, nil, [Vines::SaslErrors::TemporaryAuthFailure.new])
79
+ node = node(%Q{<auth xmlns="#{Vines::NAMESPACES[:sasl]}" mechanism="PLAIN">tokens</auth>})
80
+ assert_nothing_raised { @state.node(node) }
81
+ assert @stream.verify
82
+ end
83
+
84
+ def test_plain_auth_invalid_password
85
+ @stream.expect(:storage, MockStorage.new)
86
+ @stream.expect(:error, nil, [Vines::SaslErrors::NotAuthorized.new])
87
+ node = node(%Q{<auth xmlns="#{Vines::NAMESPACES[:sasl]}" mechanism="PLAIN">#{Base64.encode64("alice@wonderland.lit\000\000bogus")}</auth>})
88
+ assert_nothing_raised { @state.node(node) }
89
+ assert @stream.verify
90
+ end
91
+
92
+ def test_plain_auth_valid_password
93
+ user = Vines::User.new(:jid => 'alice@wonderland.lit')
94
+ @stream.expect(:storage, MockStorage.new)
95
+ @stream.expect(:user, user)
96
+ @stream.expect(:user=, nil, [user])
97
+ @stream.expect(:write, nil, [%Q{<success xmlns="#{Vines::NAMESPACES[:sasl]}"/>}])
98
+ @stream.expect(:advance, nil, [Vines::Stream::Client::BindRestart.new(@stream)])
99
+ node = node(%Q{<auth xmlns="#{Vines::NAMESPACES[:sasl]}" mechanism="PLAIN">#{Base64.encode64("alice@wonderland.lit\000\000secr3t")}</auth>})
100
+ assert_nothing_raised { @state.node(node) }
101
+ assert @stream.verify
102
+ end
103
+
104
+ def test_max_auth_attempts_policy_violation
105
+ @stream.expect(:storage, MockStorage.new)
106
+ node = proc do
107
+ node(%Q{<auth xmlns="#{Vines::NAMESPACES[:sasl]}" mechanism="PLAIN">#{Base64.encode64("alice@wonderland.lit\000\000bogus")}</auth>})
108
+ end
109
+
110
+ @stream.expect(:error, nil, [Vines::SaslErrors::NotAuthorized.new])
111
+ assert_nothing_raised { @state.node(node.call) }
112
+ assert @stream.verify
113
+
114
+ assert_nothing_raised { @state.node(node.call) }
115
+ assert @stream.verify
116
+
117
+ @stream.expect(:error, nil, [Vines::StreamErrors::PolicyViolation.new])
118
+ assert_nothing_raised { @state.node(node.call) }
119
+ assert @stream.verify
120
+ end
121
+
122
+ private
123
+
124
+ def node(xml)
125
+ Nokogiri::XML(xml).root
126
+ end
127
+ end