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.
- data/LICENSE +19 -0
- data/README +34 -0
- data/Rakefile +55 -0
- data/bin/vines +95 -0
- data/conf/certs/README +32 -0
- data/conf/certs/ca-bundle.crt +3987 -0
- data/conf/config.rb +114 -0
- data/lib/vines.rb +155 -0
- data/lib/vines/command/bcrypt.rb +12 -0
- data/lib/vines/command/cert.rb +49 -0
- data/lib/vines/command/init.rb +58 -0
- data/lib/vines/command/ldap.rb +35 -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.rb +191 -0
- data/lib/vines/contact.rb +99 -0
- data/lib/vines/daemon.rb +78 -0
- data/lib/vines/error.rb +150 -0
- data/lib/vines/jid.rb +56 -0
- data/lib/vines/kit.rb +23 -0
- data/lib/vines/router.rb +125 -0
- data/lib/vines/stanza.rb +55 -0
- data/lib/vines/stanza/iq.rb +50 -0
- data/lib/vines/stanza/iq/auth.rb +18 -0
- data/lib/vines/stanza/iq/disco_info.rb +25 -0
- data/lib/vines/stanza/iq/disco_items.rb +23 -0
- data/lib/vines/stanza/iq/error.rb +16 -0
- data/lib/vines/stanza/iq/ping.rb +16 -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 +153 -0
- data/lib/vines/stanza/iq/session.rb +22 -0
- data/lib/vines/stanza/iq/vcard.rb +58 -0
- data/lib/vines/stanza/message.rb +41 -0
- data/lib/vines/stanza/presence.rb +119 -0
- data/lib/vines/stanza/presence/error.rb +23 -0
- data/lib/vines/stanza/presence/probe.rb +38 -0
- data/lib/vines/stanza/presence/subscribe.rb +66 -0
- data/lib/vines/stanza/presence/subscribed.rb +64 -0
- data/lib/vines/stanza/presence/unavailable.rb +15 -0
- data/lib/vines/stanza/presence/unsubscribe.rb +57 -0
- data/lib/vines/stanza/presence/unsubscribed.rb +50 -0
- data/lib/vines/storage.rb +216 -0
- data/lib/vines/storage/couchdb.rb +119 -0
- data/lib/vines/storage/ldap.rb +59 -0
- data/lib/vines/storage/local.rb +66 -0
- data/lib/vines/storage/redis.rb +108 -0
- data/lib/vines/storage/sql.rb +174 -0
- data/lib/vines/store.rb +51 -0
- data/lib/vines/stream.rb +198 -0
- data/lib/vines/stream/client.rb +131 -0
- data/lib/vines/stream/client/auth.rb +94 -0
- data/lib/vines/stream/client/auth_restart.rb +33 -0
- data/lib/vines/stream/client/bind.rb +58 -0
- data/lib/vines/stream/client/bind_restart.rb +25 -0
- data/lib/vines/stream/client/closed.rb +13 -0
- data/lib/vines/stream/client/ready.rb +15 -0
- data/lib/vines/stream/client/start.rb +27 -0
- data/lib/vines/stream/client/tls.rb +37 -0
- data/lib/vines/stream/component.rb +53 -0
- data/lib/vines/stream/component/handshake.rb +25 -0
- data/lib/vines/stream/component/ready.rb +24 -0
- data/lib/vines/stream/component/start.rb +19 -0
- data/lib/vines/stream/http.rb +111 -0
- data/lib/vines/stream/http/http_request.rb +22 -0
- data/lib/vines/stream/http/http_state.rb +139 -0
- data/lib/vines/stream/http/http_states.rb +53 -0
- data/lib/vines/stream/parser.rb +78 -0
- data/lib/vines/stream/server.rb +126 -0
- data/lib/vines/stream/server/auth.rb +13 -0
- data/lib/vines/stream/server/auth_restart.rb +19 -0
- data/lib/vines/stream/server/final_restart.rb +20 -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 +28 -0
- data/lib/vines/stream/server/outbound/final_features.rb +27 -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 +31 -0
- data/lib/vines/stream/server/ready.rb +20 -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 +55 -0
- data/lib/vines/token_bucket.rb +46 -0
- data/lib/vines/user.rb +124 -0
- data/lib/vines/version.rb +5 -0
- data/lib/vines/xmpp_server.rb +25 -0
- data/test/config_test.rb +396 -0
- data/test/error_test.rb +59 -0
- data/test/ext/nokogiri.rb +14 -0
- data/test/jid_test.rb +71 -0
- data/test/kit_test.rb +21 -0
- data/test/router_test.rb +60 -0
- data/test/stanza/iq/roster_test.rb +198 -0
- data/test/stanza/iq/session_test.rb +30 -0
- data/test/stanza/iq/vcard_test.rb +159 -0
- data/test/stanza/message_test.rb +124 -0
- data/test/stanza/presence/subscribe_test.rb +75 -0
- data/test/storage/couchdb_test.rb +102 -0
- data/test/storage/ldap_test.rb +207 -0
- data/test/storage/local_test.rb +54 -0
- data/test/storage/redis_test.rb +75 -0
- data/test/storage/sql_test.rb +55 -0
- data/test/storage/storage_tests.rb +134 -0
- data/test/storage_test.rb +90 -0
- data/test/stream/client/auth_test.rb +127 -0
- data/test/stream/client/ready_test.rb +47 -0
- data/test/stream/component/handshake_test.rb +46 -0
- data/test/stream/component/ready_test.rb +105 -0
- data/test/stream/component/start_test.rb +41 -0
- data/test/stream/parser_test.rb +121 -0
- data/test/stream/server/outbound/auth_test.rb +77 -0
- data/test/stream/server/ready_test.rb +100 -0
- data/test/token_bucket_test.rb +24 -0
- data/test/user_test.rb +64 -0
- metadata +318 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stanza
|
|
5
|
+
class Presence
|
|
6
|
+
class Subscribe < Presence
|
|
7
|
+
register "/presence[@type='subscribe']"
|
|
8
|
+
|
|
9
|
+
def process
|
|
10
|
+
inbound? ? process_inbound : process_outbound
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def process_outbound
|
|
14
|
+
self['from'] = stream.user.jid.bare.to_s
|
|
15
|
+
to = stamp_to
|
|
16
|
+
route unless local?
|
|
17
|
+
|
|
18
|
+
stream.user.request_subscription(to)
|
|
19
|
+
storage.save_user(stream.user)
|
|
20
|
+
stream.update_user_streams(stream.user)
|
|
21
|
+
|
|
22
|
+
process_inbound if local?
|
|
23
|
+
|
|
24
|
+
router.interested_resources(stream.user.jid).each do |recipient|
|
|
25
|
+
send_subscribe_roster_push(recipient, stream.user.contact(to))
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def process_inbound
|
|
30
|
+
self['from'] = stream.user.jid.bare.to_s
|
|
31
|
+
to = stamp_to
|
|
32
|
+
|
|
33
|
+
contact = storage(to.domain).find_user(to)
|
|
34
|
+
if contact.nil?
|
|
35
|
+
auto_reply_to_subscription_request(to, 'unsubscribed')
|
|
36
|
+
elsif contact.subscribed_from?(stream.user.jid)
|
|
37
|
+
auto_reply_to_subscription_request(to, 'subscribed')
|
|
38
|
+
else
|
|
39
|
+
recipients = router.available_resources(to)
|
|
40
|
+
if recipients.empty?
|
|
41
|
+
# TODO store subscription request per RFC 6121 3.1.3 #4
|
|
42
|
+
else
|
|
43
|
+
recipients.each {|stream| stream.write(@node) }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def send_subscribe_roster_push(recipient, contact)
|
|
51
|
+
doc = Document.new
|
|
52
|
+
node = doc.create_element('iq') do |el|
|
|
53
|
+
el['id'] = Kit.uuid
|
|
54
|
+
el['to'] = recipient.user.jid.to_s
|
|
55
|
+
el['type'] = 'set'
|
|
56
|
+
el << doc.create_element('query') do |query|
|
|
57
|
+
query.default_namespace = NAMESPACES[:roster]
|
|
58
|
+
query << contact.to_roster_xml
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
recipient.write(node)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stanza
|
|
5
|
+
class Presence
|
|
6
|
+
class Subscribed < Presence
|
|
7
|
+
register "/presence[@type='subscribed']"
|
|
8
|
+
|
|
9
|
+
def process
|
|
10
|
+
inbound? ? process_inbound : process_outbound
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def process_outbound
|
|
14
|
+
self['from'] = stream.user.jid.bare.to_s
|
|
15
|
+
to = stamp_to
|
|
16
|
+
route unless local?
|
|
17
|
+
|
|
18
|
+
stream.user.add_subscription_from(to)
|
|
19
|
+
storage.save_user(stream.user)
|
|
20
|
+
stream.update_user_streams(stream.user)
|
|
21
|
+
|
|
22
|
+
router.interested_resources(stream.user.jid).each do |recipient|
|
|
23
|
+
send_subscribed_roster_push(recipient, to, stream.user.contact(to).subscription)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
presences = router.available_resources(stream.user.jid).map do |c|
|
|
27
|
+
doc = Document.new
|
|
28
|
+
doc.create_element('presence',
|
|
29
|
+
'from' => c.user.jid.to_s,
|
|
30
|
+
'id' => Kit.uuid,
|
|
31
|
+
'to' => to.to_s)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
if local?
|
|
35
|
+
router.available_resources(to).each do |recipient|
|
|
36
|
+
presences.each {|el| recipient.write(el) }
|
|
37
|
+
end
|
|
38
|
+
else
|
|
39
|
+
presences.each {|el| router.route(el) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
process_inbound if local?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def process_inbound
|
|
46
|
+
self['from'] = stream.user.jid.bare.to_s
|
|
47
|
+
to = stamp_to
|
|
48
|
+
|
|
49
|
+
user = storage(to.domain).find_user(to)
|
|
50
|
+
contact = user.contact(stream.user.jid) if user
|
|
51
|
+
return unless contact && contact.can_subscribe?
|
|
52
|
+
contact.subscribe_to
|
|
53
|
+
storage(to.domain).save_user(user)
|
|
54
|
+
stream.update_user_streams(user)
|
|
55
|
+
|
|
56
|
+
router.interested_resources(to).each do |recipient|
|
|
57
|
+
recipient.write(@node)
|
|
58
|
+
send_subscribed_roster_push(recipient, stream.user.jid.bare, contact.subscription)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -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,57 @@
|
|
|
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
|
+
inbound? ? process_inbound : process_outbound
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def process_outbound
|
|
14
|
+
self['from'] = stream.user.jid.bare.to_s
|
|
15
|
+
to = stamp_to
|
|
16
|
+
route unless local?
|
|
17
|
+
|
|
18
|
+
stream.user.remove_subscription_to(to)
|
|
19
|
+
storage.save_user(stream.user)
|
|
20
|
+
stream.update_user_streams(stream.user)
|
|
21
|
+
|
|
22
|
+
process_inbound if local?
|
|
23
|
+
|
|
24
|
+
if contact = stream.user.contact(to)
|
|
25
|
+
router.interested_resources(stream.user.jid).each do |recipient|
|
|
26
|
+
send_subscribed_roster_push(recipient, to, contact.subscription)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def process_inbound
|
|
32
|
+
self['from'] = stream.user.jid.bare.to_s
|
|
33
|
+
to = stamp_to
|
|
34
|
+
|
|
35
|
+
user = storage(to.domain).find_user(to)
|
|
36
|
+
return unless user && user.subscribed_from?(stream.user.jid)
|
|
37
|
+
contact = user.contact(stream.user.jid)
|
|
38
|
+
contact.unsubscribe_from
|
|
39
|
+
storage(to.domain).save_user(user)
|
|
40
|
+
stream.update_user_streams(user)
|
|
41
|
+
|
|
42
|
+
router.interested_resources(to).each do |recipient|
|
|
43
|
+
recipient.write(@node)
|
|
44
|
+
send_subscribed_roster_push(recipient, stream.user.jid.bare, contact.subscription)
|
|
45
|
+
doc = Document.new
|
|
46
|
+
el = doc.create_element('presence',
|
|
47
|
+
'from' => stream.user.jid.bare.to_s,
|
|
48
|
+
'id' => Kit.uuid,
|
|
49
|
+
'to' => recipient.user.jid.to_s,
|
|
50
|
+
'type' => 'unavailable')
|
|
51
|
+
recipient.write(el)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
inbound? ? process_inbound : process_outbound
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def process_outbound
|
|
14
|
+
self['from'] = stream.user.jid.bare.to_s
|
|
15
|
+
to = stamp_to
|
|
16
|
+
route unless local?
|
|
17
|
+
|
|
18
|
+
stream.user.remove_subscription_from(to)
|
|
19
|
+
storage.save_user(stream.user)
|
|
20
|
+
stream.update_user_streams(stream.user)
|
|
21
|
+
|
|
22
|
+
if contact = stream.user.contact(to)
|
|
23
|
+
router.interested_resources(stream.user.jid).each do |recipient|
|
|
24
|
+
send_subscribed_roster_push(recipient, to, contact.subscription)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
process_inbound if local?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def process_inbound
|
|
32
|
+
self['from'] = stream.user.jid.bare.to_s
|
|
33
|
+
to = stamp_to
|
|
34
|
+
|
|
35
|
+
user = storage(to.domain).find_user(to)
|
|
36
|
+
return unless user && user.subscribed_to?(stream.user.jid)
|
|
37
|
+
contact = user.contact(stream.user.jid)
|
|
38
|
+
contact.unsubscribe_to
|
|
39
|
+
storage(to.domain).save_user(user)
|
|
40
|
+
stream.update_user_streams(user)
|
|
41
|
+
|
|
42
|
+
router.interested_resources(to).each do |recipient|
|
|
43
|
+
recipient.write(@node)
|
|
44
|
+
send_subscribed_roster_push(recipient, stream.user.jid.bare, contact.subscription)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Storage
|
|
5
|
+
include Vines::Log
|
|
6
|
+
|
|
7
|
+
attr_accessor :ldap
|
|
8
|
+
|
|
9
|
+
@@nicks = {}
|
|
10
|
+
|
|
11
|
+
# Register a nickname that can be used in the config file to specify this
|
|
12
|
+
# storage implementation.
|
|
13
|
+
def self.register(name)
|
|
14
|
+
@@nicks[name.to_sym] = self
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.from_name(name, &block)
|
|
18
|
+
klass = @@nicks[name.to_sym]
|
|
19
|
+
raise "#{name} storage class not found" unless klass
|
|
20
|
+
klass.new(&block)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Wrap a blocking IO method in a new method that pushes the original method
|
|
24
|
+
# onto EventMachine's thread pool using EM#defer. Storage classes implemented
|
|
25
|
+
# with blocking IO don't need to worry about threading or blocking the the
|
|
26
|
+
# EventMachine reactor thread if they wrap their methods with this one.
|
|
27
|
+
#
|
|
28
|
+
# For example:
|
|
29
|
+
# def find_user(jid)
|
|
30
|
+
# some_blocking_lookup(jid)
|
|
31
|
+
# end
|
|
32
|
+
# defer :find_user
|
|
33
|
+
#
|
|
34
|
+
# Storage classes that use asynchronous IO (through an EventMachine
|
|
35
|
+
# enabled library like em-http-request or em-redis) don't need any special
|
|
36
|
+
# consideration and must not use this method.
|
|
37
|
+
def self.defer(method)
|
|
38
|
+
old = "_deferred_#{method}"
|
|
39
|
+
alias_method old, method
|
|
40
|
+
define_method method do |*args|
|
|
41
|
+
fiber = Fiber.current
|
|
42
|
+
op = proc do
|
|
43
|
+
begin
|
|
44
|
+
method(old).call(*args)
|
|
45
|
+
rescue Exception => e
|
|
46
|
+
log.error("Thread pool operation failed: #{e.message}")
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
cb = proc {|result| fiber.resume(result) }
|
|
51
|
+
EM.defer(op, cb)
|
|
52
|
+
Fiber.yield
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Wrap an authenticate method with a new method that uses LDAP if it's
|
|
57
|
+
# enabled in the config file. If LDAP is not enabled, invoke the original
|
|
58
|
+
# authenticate method as usual. This allows storage classes to implement
|
|
59
|
+
# their native authentication logic and not worry about handling LDAP.
|
|
60
|
+
#
|
|
61
|
+
# For example:
|
|
62
|
+
# def authenticate(username, password)
|
|
63
|
+
# some_user_lookup_by_password(username, password)
|
|
64
|
+
# end
|
|
65
|
+
# wrap_ldap :authenticate
|
|
66
|
+
def self.wrap_ldap(method)
|
|
67
|
+
old = "_ldap_#{method}"
|
|
68
|
+
alias_method old, method
|
|
69
|
+
define_method method do |*args|
|
|
70
|
+
ldap? ? authenticate_with_ldap(*args) : method(old).call(*args)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Wrap a method with Fiber yield and resume logic. The method must yield
|
|
75
|
+
# its result to a block. This makes it easier to write asynchronous
|
|
76
|
+
# implementations of +authenticate+, +find_user+, and +save_user+ that
|
|
77
|
+
# block and return a result rather than yielding.
|
|
78
|
+
#
|
|
79
|
+
# For example:
|
|
80
|
+
# def find_user(jid)
|
|
81
|
+
# http = EM::HttpRequest.new(url).get
|
|
82
|
+
# http.callback { yield build_user_from_http_response(http) }
|
|
83
|
+
# end
|
|
84
|
+
# fiber :find_user
|
|
85
|
+
#
|
|
86
|
+
# Because +find_user+ has been wrapped in Fiber logic, we can call it
|
|
87
|
+
# synchronously even though it uses asynchronous EventMachine calls.
|
|
88
|
+
#
|
|
89
|
+
# user = storage.find_user('alice@wonderland.lit')
|
|
90
|
+
# puts user.nil?
|
|
91
|
+
def self.fiber(method)
|
|
92
|
+
old = "_fiber_#{method}"
|
|
93
|
+
alias_method old, method
|
|
94
|
+
define_method method do |*args|
|
|
95
|
+
fiber, yielding = Fiber.current, true
|
|
96
|
+
method(old).call(*args) do |user|
|
|
97
|
+
fiber.resume(user) rescue yielding = false
|
|
98
|
+
end
|
|
99
|
+
Fiber.yield if yielding
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Return true if users are authenticated against an LDAP directory.
|
|
104
|
+
def ldap?
|
|
105
|
+
!!ldap
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Validate the username and password pair and return a Vines::User object
|
|
109
|
+
# on success. Return nil on failure.
|
|
110
|
+
#
|
|
111
|
+
# For example:
|
|
112
|
+
# user = storage.authenticate('alice@wonderland.lit', 'secr3t')
|
|
113
|
+
# puts user.nil?
|
|
114
|
+
#
|
|
115
|
+
# This default implementation validates the password against a bcrypt hash
|
|
116
|
+
# of the password stored in the database. Sub-classes not using bcrypt
|
|
117
|
+
# passwords must override this method.
|
|
118
|
+
def authenticate(username, password)
|
|
119
|
+
user = find_user(username)
|
|
120
|
+
hash = BCrypt::Password.new(user.password) rescue nil
|
|
121
|
+
(hash && hash == password) ? user : nil
|
|
122
|
+
end
|
|
123
|
+
wrap_ldap :authenticate
|
|
124
|
+
|
|
125
|
+
# Return the Vines::User associated with the JID. Return nil if the user
|
|
126
|
+
# could not be found. JID may be +nil+, a +String+, or a +Vines::JID+
|
|
127
|
+
# object. It may be a bare JID or a full JID. Implementations of this method
|
|
128
|
+
# must convert the JID to a bare JID before searching for the user in the
|
|
129
|
+
# database.
|
|
130
|
+
#
|
|
131
|
+
# user = storage.find_user('alice@wonderland.lit')
|
|
132
|
+
# puts user.nil?
|
|
133
|
+
def find_user(jid)
|
|
134
|
+
raise 'subclass must implement'
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Persist the Vines::User object to the database and return when the save
|
|
138
|
+
# is complete.
|
|
139
|
+
#
|
|
140
|
+
# alice = Vines::User.new(:jid => 'alice@wonderland.lit')
|
|
141
|
+
# storage.save_user(alice)
|
|
142
|
+
# puts 'saved'
|
|
143
|
+
def save_user(user)
|
|
144
|
+
raise 'subclass must implement'
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Return the Nokogiri::XML::Node for the vcard stored for this JID. Return
|
|
148
|
+
# nil if the vcard could not be found. JID may be +nil+, a +String+, or a
|
|
149
|
+
# +Vines::JID+ object. It may be a bare JID or a full JID. Implementations
|
|
150
|
+
# of this method must convert the JID to a bare JID before searching for the
|
|
151
|
+
# vcard in the database.
|
|
152
|
+
#
|
|
153
|
+
# card = storage.find_vcard('alice@wonderland.lit')
|
|
154
|
+
# puts card.nil?
|
|
155
|
+
def find_vcard(jid)
|
|
156
|
+
raise 'subclass must implement'
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Save the vcard to the database and return when the save is complete. JID
|
|
160
|
+
# may be a +String+ or a +Vines::JID+ object. It may be a bare JID or a
|
|
161
|
+
# full JID. Implementations of this method must convert the JID to a bare
|
|
162
|
+
# JID before saving the vcard. Card is a +Nokogiri::XML::Node+ object.
|
|
163
|
+
#
|
|
164
|
+
# card = Nokogiri::XML('<vCard>...</vCard>').root
|
|
165
|
+
# storage.save_vcard('alice@wonderland.lit', card)
|
|
166
|
+
# puts 'saved'
|
|
167
|
+
def save_vcard(jid, card)
|
|
168
|
+
raise 'subclass must implement'
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
private
|
|
172
|
+
|
|
173
|
+
# Return true if any of the arguments are nil or empty strings.
|
|
174
|
+
# For example:
|
|
175
|
+
# username, password = 'alice@wonderland.lit', ''
|
|
176
|
+
# empty?(username, password) #=> true
|
|
177
|
+
def empty?(*args)
|
|
178
|
+
args.flatten.any? {|arg| (arg || '').strip.empty? }
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Return a Vines::User object if we are able to bind to the LDAP server
|
|
182
|
+
# using the username and password. Return nil if authentication failed. If
|
|
183
|
+
# authentication succeeds, but the user is not yet stored in our database,
|
|
184
|
+
# save the user to the database.
|
|
185
|
+
def authenticate_with_ldap(username, password, &block)
|
|
186
|
+
if empty?(username, password)
|
|
187
|
+
block.call; return
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
op = proc do
|
|
191
|
+
begin
|
|
192
|
+
ldap.authenticate(username, password)
|
|
193
|
+
rescue Exception => e
|
|
194
|
+
log.error("LDAP authentication failed: #{e.message}")
|
|
195
|
+
nil
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
cb = proc {|user| save_ldap_user(user, &block) }
|
|
199
|
+
EM.defer(op, cb)
|
|
200
|
+
end
|
|
201
|
+
fiber :authenticate_with_ldap
|
|
202
|
+
|
|
203
|
+
def save_ldap_user(user, &block)
|
|
204
|
+
Fiber.new do
|
|
205
|
+
if user.nil?
|
|
206
|
+
block.call
|
|
207
|
+
elsif found = find_user(user.jid)
|
|
208
|
+
block.call(found)
|
|
209
|
+
else
|
|
210
|
+
save_user(user)
|
|
211
|
+
block.call(user)
|
|
212
|
+
end
|
|
213
|
+
end.resume
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|