vinesmod 0.4.5
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.
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +43 -0
- data/Rakefile +57 -0
- data/bin/vines +93 -0
- data/conf/certs/README +39 -0
- data/conf/certs/ca-bundle.crt +3366 -0
- data/conf/config.rb +149 -0
- data/lib/vines.rb +197 -0
- data/lib/vines/cluster.rb +246 -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/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/register.rb +27 -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/command/unregister.rb +27 -0
- data/lib/vines/config.rb +213 -0
- data/lib/vines/config/host.rb +119 -0
- data/lib/vines/config/port.rb +132 -0
- data/lib/vines/config/pubsub.rb +108 -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 +35 -0
- data/lib/vines/log.rb +24 -0
- data/lib/vines/router.rb +179 -0
- data/lib/vines/stanza.rb +175 -0
- data/lib/vines/stanza/iq.rb +48 -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/register.rb +42 -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/message.rb +43 -0
- data/lib/vines/stanza/presence.rb +156 -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/pubsub.rb +22 -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/storage.rb +188 -0
- data/lib/vines/storage/local.rb +165 -0
- data/lib/vines/storage/null.rb +39 -0
- data/lib/vines/storage/sql.rb +260 -0
- data/lib/vines/store.rb +94 -0
- data/lib/vines/stream.rb +247 -0
- data/lib/vines/stream/client.rb +84 -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/component.rb +58 -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/http.rb +157 -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/parser.rb +78 -0
- data/lib/vines/stream/sasl.rb +92 -0
- data/lib/vines/stream/server.rb +150 -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/state.rb +60 -0
- data/lib/vines/token_bucket.rb +55 -0
- data/lib/vines/user.rb +123 -0
- data/lib/vines/version.rb +5 -0
- data/lib/vines/xmpp_server.rb +43 -0
- data/vines.gemspec +36 -0
- data/web/404.html +51 -0
- data/web/apple-touch-icon.png +0 -0
- data/web/chat/coffeescripts/chat.coffee +362 -0
- data/web/chat/coffeescripts/init.coffee +15 -0
- data/web/chat/index.html +16 -0
- data/web/chat/javascripts/app.js +1 -0
- data/web/chat/stylesheets/chat.css +144 -0
- data/web/favicon.png +0 -0
- data/web/lib/coffeescripts/button.coffee +25 -0
- data/web/lib/coffeescripts/contact.coffee +32 -0
- data/web/lib/coffeescripts/filter.coffee +49 -0
- data/web/lib/coffeescripts/layout.coffee +30 -0
- data/web/lib/coffeescripts/login.coffee +68 -0
- data/web/lib/coffeescripts/logout.coffee +5 -0
- data/web/lib/coffeescripts/navbar.coffee +84 -0
- data/web/lib/coffeescripts/notification.coffee +14 -0
- data/web/lib/coffeescripts/router.coffee +40 -0
- data/web/lib/coffeescripts/session.coffee +229 -0
- data/web/lib/coffeescripts/transfer.coffee +106 -0
- data/web/lib/images/dark-gray.png +0 -0
- data/web/lib/images/default-user.png +0 -0
- data/web/lib/images/light-gray.png +0 -0
- data/web/lib/images/logo-large.png +0 -0
- data/web/lib/images/logo-small.png +0 -0
- data/web/lib/images/white.png +0 -0
- data/web/lib/javascripts/base.js +12 -0
- data/web/lib/javascripts/icons.js +110 -0
- data/web/lib/javascripts/jquery.cookie.js +91 -0
- data/web/lib/javascripts/jquery.js +4 -0
- data/web/lib/javascripts/raphael.js +6 -0
- data/web/lib/javascripts/strophe.js +1 -0
- data/web/lib/stylesheets/base.css +385 -0
- data/web/lib/stylesheets/login.css +68 -0
- metadata +423 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stanza
|
5
|
+
class Presence
|
6
|
+
class Unavailable < Presence
|
7
|
+
register "/presence[@type='unavailable']"
|
8
|
+
|
9
|
+
def process
|
10
|
+
inbound? ? inbound_broadcast_presence : outbound_broadcast_presence
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stanza
|
5
|
+
class Presence
|
6
|
+
class Unsubscribe < Presence
|
7
|
+
register "/presence[@type='unsubscribe']"
|
8
|
+
|
9
|
+
def process
|
10
|
+
stamp_from
|
11
|
+
inbound? ? process_inbound : process_outbound
|
12
|
+
end
|
13
|
+
|
14
|
+
def process_outbound
|
15
|
+
to = stamp_to
|
16
|
+
return unless stream.user.subscribed_to?(to)
|
17
|
+
stream.user.remove_subscription_to(to)
|
18
|
+
storage.save_user(stream.user)
|
19
|
+
stream.update_user_streams(stream.user)
|
20
|
+
local? ? process_inbound : route
|
21
|
+
send_roster_push(to)
|
22
|
+
end
|
23
|
+
|
24
|
+
def process_inbound
|
25
|
+
to = stamp_to
|
26
|
+
user = storage(to.domain).find_user(to)
|
27
|
+
return unless user && user.subscribed_from?(stream.user.jid)
|
28
|
+
contact = user.contact(stream.user.jid)
|
29
|
+
contact.unsubscribe_from
|
30
|
+
storage(to.domain).save_user(user)
|
31
|
+
stream.update_user_streams(user)
|
32
|
+
broadcast_subscription_change(contact)
|
33
|
+
send_unavailable(to, stream.user.jid.bare)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stanza
|
5
|
+
class Presence
|
6
|
+
class Unsubscribed < Presence
|
7
|
+
register "/presence[@type='unsubscribed']"
|
8
|
+
|
9
|
+
def process
|
10
|
+
stamp_from
|
11
|
+
inbound? ? process_inbound : process_outbound
|
12
|
+
end
|
13
|
+
|
14
|
+
def process_outbound
|
15
|
+
to = stamp_to
|
16
|
+
return unless stream.user.subscribed_from?(to)
|
17
|
+
send_unavailable(stream.user.jid, to)
|
18
|
+
stream.user.remove_subscription_from(to)
|
19
|
+
storage.save_user(stream.user)
|
20
|
+
stream.update_user_streams(stream.user)
|
21
|
+
local? ? process_inbound : route
|
22
|
+
send_roster_push(to)
|
23
|
+
end
|
24
|
+
|
25
|
+
def process_inbound
|
26
|
+
to = stamp_to
|
27
|
+
user = storage(to.domain).find_user(to)
|
28
|
+
return unless user && user.subscribed_to?(stream.user.jid)
|
29
|
+
contact = user.contact(stream.user.jid)
|
30
|
+
contact.unsubscribe_to
|
31
|
+
storage(to.domain).save_user(user)
|
32
|
+
stream.update_user_streams(user)
|
33
|
+
broadcast_subscription_change(contact)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stanza
|
5
|
+
class PubSub < Iq
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
# Return the Config::PubSub system for the domain to which this stanza is
|
10
|
+
# addressed or nil if it's not to a pubsub subdomain.
|
11
|
+
def pubsub
|
12
|
+
stream.config.pubsub(validate_to)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Raise feature-not-implemented if this stanza is addressed to the chat
|
16
|
+
# server itself, rather than a pubsub subdomain.
|
17
|
+
def validate_to_address
|
18
|
+
raise StanzaErrors::FeatureNotImplemented.new(self, 'cancel') unless pubsub
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stanza
|
5
|
+
class PubSub
|
6
|
+
class Create < PubSub
|
7
|
+
NS = NAMESPACES[:pubsub]
|
8
|
+
|
9
|
+
register "/iq[@id and @type='set']/ns:pubsub/ns:create", 'ns' => NS
|
10
|
+
|
11
|
+
def process
|
12
|
+
return if route_iq || !allowed?
|
13
|
+
validate_to_address
|
14
|
+
|
15
|
+
node = self.xpath('ns:pubsub/ns:create', 'ns' => NS)
|
16
|
+
raise StanzaErrors::BadRequest.new(self, 'modify') if node.size != 1
|
17
|
+
node = node.first
|
18
|
+
|
19
|
+
id = (node['node'] || '').strip
|
20
|
+
id = Kit.uuid if id.empty?
|
21
|
+
raise StanzaErrors::Conflict.new(self, 'cancel') if pubsub.node?(id)
|
22
|
+
pubsub.add_node(id)
|
23
|
+
send_result_iq(id)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def send_result_iq(id)
|
29
|
+
el = to_result
|
30
|
+
el << el.document.create_element('pubsub') do |node|
|
31
|
+
node.default_namespace = NS
|
32
|
+
node << el.document.create_element('create', 'node' => id)
|
33
|
+
end
|
34
|
+
stream.write(el)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stanza
|
5
|
+
class PubSub
|
6
|
+
class Delete < PubSub
|
7
|
+
NS = NAMESPACES[:pubsub]
|
8
|
+
|
9
|
+
register "/iq[@id and @type='set']/ns:pubsub/ns:delete", 'ns' => NS
|
10
|
+
|
11
|
+
def process
|
12
|
+
return if route_iq || !allowed?
|
13
|
+
validate_to_address
|
14
|
+
|
15
|
+
node = self.xpath('ns:pubsub/ns:delete', 'ns' => NS)
|
16
|
+
raise StanzaErrors::BadRequest.new(self, 'modify') if node.size != 1
|
17
|
+
node = node.first
|
18
|
+
|
19
|
+
id = node['node']
|
20
|
+
raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless pubsub.node?(id)
|
21
|
+
|
22
|
+
pubsub.publish(id, message(id))
|
23
|
+
pubsub.delete_node(id)
|
24
|
+
stream.write(to_result)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def message(id)
|
30
|
+
doc = Document.new
|
31
|
+
doc.create_element('message') do |node|
|
32
|
+
node << node.document.create_element('event') do |event|
|
33
|
+
event.default_namespace = NAMESPACES[:pubsub_event]
|
34
|
+
event << node.document.create_element('delete', 'node' => id)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stanza
|
5
|
+
class PubSub
|
6
|
+
class Publish < PubSub
|
7
|
+
NS = NAMESPACES[:pubsub]
|
8
|
+
|
9
|
+
register "/iq[@id and @type='set']/ns:pubsub/ns:publish", 'ns' => NS
|
10
|
+
|
11
|
+
def process
|
12
|
+
return if route_iq || !allowed?
|
13
|
+
validate_to_address
|
14
|
+
|
15
|
+
node = self.xpath('ns:pubsub/ns:publish', 'ns' => NS)
|
16
|
+
raise StanzaErrors::BadRequest.new(self, 'modify') if node.size != 1
|
17
|
+
node = node.first
|
18
|
+
id = node['node']
|
19
|
+
|
20
|
+
raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless pubsub.node?(id)
|
21
|
+
|
22
|
+
item = node.xpath('ns:item', 'ns' => NS)
|
23
|
+
raise StanzaErrors::BadRequest.new(self, 'modify') unless item.size == 1
|
24
|
+
item = item.first
|
25
|
+
unless item['id']
|
26
|
+
item['id'] = Kit.uuid
|
27
|
+
include_item = true
|
28
|
+
end
|
29
|
+
|
30
|
+
raise StanzaErrors::BadRequest.new(self, 'modify') unless item.elements.size == 1
|
31
|
+
pubsub.publish(id, message(id, item))
|
32
|
+
send_result_iq(id, include_item ? item : nil)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def message(node, item)
|
38
|
+
doc = Document.new
|
39
|
+
doc.create_element('message') do |message|
|
40
|
+
message << doc.create_element('event') do |event|
|
41
|
+
event.default_namespace = NAMESPACES[:pubsub_event]
|
42
|
+
event << doc.create_element('items', 'node' => node) do |items|
|
43
|
+
items << doc.create_element('item', 'id' => item['id'], 'publisher' => stream.user.jid.to_s) do |el|
|
44
|
+
el << item.elements.first
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def send_result_iq(node, item)
|
52
|
+
result = to_result
|
53
|
+
if item
|
54
|
+
result << result.document.create_element('pubsub') do |pubsub|
|
55
|
+
pubsub.default_namespace = NS
|
56
|
+
pubsub << result.document.create_element('publish', 'node' => node) do |publish|
|
57
|
+
publish << result.document.create_element('item', 'id' => item['id'])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
stream.write(result)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stanza
|
5
|
+
class PubSub
|
6
|
+
class Subscribe < PubSub
|
7
|
+
NS = NAMESPACES[:pubsub]
|
8
|
+
|
9
|
+
register "/iq[@id and @type='set']/ns:pubsub/ns:subscribe", 'ns' => NS
|
10
|
+
|
11
|
+
def process
|
12
|
+
return if route_iq || !allowed?
|
13
|
+
validate_to_address
|
14
|
+
|
15
|
+
node = self.xpath('ns:pubsub/ns:subscribe', 'ns' => NS)
|
16
|
+
raise StanzaErrors::BadRequest.new(self, 'modify') if node.size != 1
|
17
|
+
node = node.first
|
18
|
+
id, jid = node['node'], JID.new(node['jid'])
|
19
|
+
|
20
|
+
raise StanzaErrors::BadRequest.new(self, 'modify') unless stream.user.jid.bare == jid.bare
|
21
|
+
raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless pubsub.node?(id)
|
22
|
+
raise StanzaErrors::PolicyViolation.new(self, 'wait') if pubsub.subscribed?(id, jid)
|
23
|
+
|
24
|
+
pubsub.subscribe(id, jid)
|
25
|
+
send_result_iq(id, jid)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def send_result_iq(id, jid)
|
31
|
+
result = to_result
|
32
|
+
result << result.document.create_element('pubsub') do |node|
|
33
|
+
node.default_namespace = NS
|
34
|
+
node << result.document.create_element('subscription',
|
35
|
+
'node' => id,
|
36
|
+
'jid' => jid.to_s,
|
37
|
+
'subscription' => 'subscribed')
|
38
|
+
end
|
39
|
+
stream.write(result)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stanza
|
5
|
+
class PubSub
|
6
|
+
class Unsubscribe < PubSub
|
7
|
+
NS = NAMESPACES[:pubsub]
|
8
|
+
|
9
|
+
register "/iq[@id and @type='set']/ns:pubsub/ns:unsubscribe", 'ns' => NS
|
10
|
+
|
11
|
+
def process
|
12
|
+
return if route_iq || !allowed?
|
13
|
+
validate_to_address
|
14
|
+
|
15
|
+
node = self.xpath('ns:pubsub/ns:unsubscribe', 'ns' => NS)
|
16
|
+
raise StanzaErrors::BadRequest.new(self, 'modify') if node.size != 1
|
17
|
+
node = node.first
|
18
|
+
id, jid = node['node'], JID.new(node['jid'])
|
19
|
+
|
20
|
+
raise StanzaErrors::Forbidden.new(self, 'auth') unless stream.user.jid.bare == jid.bare
|
21
|
+
raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless pubsub.node?(id)
|
22
|
+
raise StanzaErrors::UnexpectedRequest.new(self, 'cancel') unless pubsub.subscribed?(id, jid)
|
23
|
+
|
24
|
+
pubsub.unsubscribe(id, jid)
|
25
|
+
stream.write(to_result)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Storage
|
5
|
+
include Vines::Log
|
6
|
+
|
7
|
+
@@nicks = {}
|
8
|
+
|
9
|
+
# Register a nickname that can be used in the config file to specify this
|
10
|
+
# storage implementation.
|
11
|
+
def self.register(name)
|
12
|
+
@@nicks[name.to_sym] = self
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.from_name(name, &block)
|
16
|
+
klass = @@nicks[name.to_sym]
|
17
|
+
raise "#{name} storage class not found" unless klass
|
18
|
+
klass.new(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Wrap a blocking IO method in a new method that pushes the original method
|
22
|
+
# onto EventMachine's thread pool using EM#defer. Storage classes implemented
|
23
|
+
# with blocking IO don't need to worry about threading or blocking the
|
24
|
+
# EventMachine reactor thread if they wrap their methods with this one.
|
25
|
+
#
|
26
|
+
# For example:
|
27
|
+
# def find_user(jid)
|
28
|
+
# some_blocking_lookup(jid)
|
29
|
+
# end
|
30
|
+
# defer :find_user
|
31
|
+
#
|
32
|
+
# Storage classes that use asynchronous IO (through an EventMachine
|
33
|
+
# enabled library like em-http-request or em-redis) don't need any special
|
34
|
+
# consideration and must not use this method.
|
35
|
+
def self.defer(method)
|
36
|
+
old = instance_method(method)
|
37
|
+
define_method method do |*args|
|
38
|
+
fiber = Fiber.current
|
39
|
+
op = operation { old.bind(self).call(*args) }
|
40
|
+
cb = proc {|result| fiber.resume(result) }
|
41
|
+
EM.defer(op, cb)
|
42
|
+
Fiber.yield
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Wrap a method with Fiber yield and resume logic. The method must yield
|
47
|
+
# its result to a block. This makes it easier to write asynchronous
|
48
|
+
# implementations of +authenticate+, +find_user+, and +save_user+ that
|
49
|
+
# block and return a result rather than yielding.
|
50
|
+
#
|
51
|
+
# For example:
|
52
|
+
# def find_user(jid)
|
53
|
+
# http = EM::HttpRequest.new(url).get
|
54
|
+
# http.callback { yield build_user_from_http_response(http) }
|
55
|
+
# end
|
56
|
+
# fiber :find_user
|
57
|
+
#
|
58
|
+
# Because +find_user+ has been wrapped in Fiber logic, we can call it
|
59
|
+
# synchronously even though it uses asynchronous EventMachine calls.
|
60
|
+
#
|
61
|
+
# user = storage.find_user('alice@wonderland.lit')
|
62
|
+
# puts user.nil?
|
63
|
+
def self.fiber(method)
|
64
|
+
old = instance_method(method)
|
65
|
+
define_method method do |*args|
|
66
|
+
fiber, yielding = Fiber.current, true
|
67
|
+
old.bind(self).call(*args) do |user|
|
68
|
+
fiber.resume(user) rescue yielding = false
|
69
|
+
end
|
70
|
+
Fiber.yield if yielding
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Validate the username and password pair and return a +Vines::User+ object
|
75
|
+
# on success. Return +nil+ on failure.
|
76
|
+
#
|
77
|
+
# For example:
|
78
|
+
# user = storage.authenticate('alice@wonderland.lit', 'secr3t')
|
79
|
+
# puts user.nil?
|
80
|
+
#
|
81
|
+
# This default implementation validates the password against a bcrypt hash
|
82
|
+
# of the password stored in the database. Sub-classes not using bcrypt
|
83
|
+
# passwords must override this method.
|
84
|
+
def authenticate(username, password)
|
85
|
+
user = find_user(username)
|
86
|
+
hash = BCrypt::Password.new(user.password) rescue nil
|
87
|
+
(hash && hash == password) ? user : nil
|
88
|
+
end
|
89
|
+
|
90
|
+
# Return the +Vines::User+ associated with the JID. Return +nil+ if the user
|
91
|
+
# could not be found. JID may be +nil+, a +String+, or a +Vines::JID+
|
92
|
+
# object. It may be a bare JID or a full JID. Implementations of this method
|
93
|
+
# must convert the JID to a bare JID before searching for the user in the
|
94
|
+
# database.
|
95
|
+
#
|
96
|
+
# user = storage.find_user('alice@wonderland.lit')
|
97
|
+
# puts user.nil?
|
98
|
+
def find_user(jid)
|
99
|
+
raise 'subclass must implement'
|
100
|
+
end
|
101
|
+
|
102
|
+
# Persist the +Vines::User+ object to the database and return when the save
|
103
|
+
# is complete.
|
104
|
+
#
|
105
|
+
# alice = Vines::User.new(:jid => 'alice@wonderland.lit')
|
106
|
+
# storage.save_user(alice)
|
107
|
+
# puts 'saved'
|
108
|
+
def save_user(user)
|
109
|
+
raise 'subclass must implement'
|
110
|
+
end
|
111
|
+
|
112
|
+
# Return the Nokogiri::XML::Node for the vcard stored for this JID. Return
|
113
|
+
# nil if the vcard could not be found. JID may be +nil+, a +String+, or a
|
114
|
+
# +Vines::JID+ object. It may be a bare JID or a full JID. Implementations
|
115
|
+
# of this method must convert the JID to a bare JID before searching for the
|
116
|
+
# vcard in the database.
|
117
|
+
#
|
118
|
+
# card = storage.find_vcard('alice@wonderland.lit')
|
119
|
+
# puts card.nil?
|
120
|
+
def find_vcard(jid)
|
121
|
+
raise 'subclass must implement'
|
122
|
+
end
|
123
|
+
|
124
|
+
# Save the vcard to the database and return when the save is complete. JID
|
125
|
+
# may be a +String+ or a +Vines::JID+ object. It may be a bare JID or a
|
126
|
+
# full JID. Implementations of this method must convert the JID to a bare
|
127
|
+
# JID before saving the vcard. Card is a +Nokogiri::XML::Node+ object.
|
128
|
+
#
|
129
|
+
# card = Nokogiri::XML('<vCard>...</vCard>').root
|
130
|
+
# storage.save_vcard('alice@wonderland.lit', card)
|
131
|
+
# puts 'saved'
|
132
|
+
def save_vcard(jid, card)
|
133
|
+
raise 'subclass must implement'
|
134
|
+
end
|
135
|
+
|
136
|
+
# Return the Nokogiri::XML::Node for the XML fragment stored for this JID.
|
137
|
+
# Return nil if the fragment could not be found. JID may be +nil+, a
|
138
|
+
# +String+, or a +Vines::JID+ object. It may be a bare JID or a full JID.
|
139
|
+
# Implementations of this method must convert the JID to a bare JID before
|
140
|
+
# searching for the fragment in the database.
|
141
|
+
#
|
142
|
+
# Private XML storage uniquely identifies fragments by JID, root element name,
|
143
|
+
# and root element namespace.
|
144
|
+
#
|
145
|
+
# root = Nokogiri::XML('<custom xmlns="urn:custom:ns"/>').root
|
146
|
+
# fragment = storage.find_fragment('alice@wonderland.lit', root)
|
147
|
+
# puts fragment.nil?
|
148
|
+
def find_fragment(jid, node)
|
149
|
+
raise 'subclass must implement'
|
150
|
+
end
|
151
|
+
|
152
|
+
# Save the XML fragment to the database and return when the save is complete.
|
153
|
+
# JID may be a +String+ or a +Vines::JID+ object. It may be a bare JID or a
|
154
|
+
# full JID. Implementations of this method must convert the JID to a bare
|
155
|
+
# JID before saving the fragment. Fragment is a +Nokogiri::XML::Node+ object.
|
156
|
+
#
|
157
|
+
# fragment = Nokogiri::XML('<custom xmlns="urn:custom:ns">some data</custom>').root
|
158
|
+
# storage.save_fragment('alice@wonderland.lit', fragment)
|
159
|
+
# puts 'saved'
|
160
|
+
def save_fragment(jid, fragment)
|
161
|
+
raise 'subclass must implement'
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
# Return true if any of the arguments are nil or empty strings.
|
167
|
+
# For example:
|
168
|
+
# username, password = 'alice@wonderland.lit', ''
|
169
|
+
# empty?(username, password) #=> true
|
170
|
+
def empty?(*args)
|
171
|
+
args.flatten.any? {|arg| (arg || '').strip.empty? }
|
172
|
+
end
|
173
|
+
|
174
|
+
# Return a +proc+ suitable for running on the +EM.defer+ thread pool that traps
|
175
|
+
# and logs any errors thrown by the provided block.
|
176
|
+
def operation
|
177
|
+
proc do
|
178
|
+
begin
|
179
|
+
yield
|
180
|
+
rescue => e
|
181
|
+
log.error("Thread pool operation failed: #{e.message}")
|
182
|
+
nil
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
end
|