vines 0.4.9 → 0.4.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/vines.rb +1 -0
- data/lib/vines/cluster/subscriber.rb +27 -2
- data/lib/vines/storage.rb +157 -80
- data/lib/vines/store.rb +58 -15
- data/lib/vines/stream.rb +71 -13
- data/lib/vines/stream/client.rb +10 -6
- data/lib/vines/stream/component.rb +3 -3
- data/lib/vines/stream/http.rb +35 -9
- data/lib/vines/stream/http/request.rb +26 -5
- data/lib/vines/stream/http/session.rb +8 -0
- data/lib/vines/stream/server.rb +13 -7
- data/lib/vines/version.rb +1 -1
- data/test/store_test.rb +80 -48
- data/test/stream/http/request_test.rb +45 -60
- data/vines.gemspec +6 -6
- metadata +12 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 377b65dfeb6ebd0ed33d7dcd0ddb99e125114597
|
4
|
+
data.tar.gz: 0a3addc473971d37aad7c4011909e22162fcf382
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a10339115953bedabc5b5fae4ec56cd4281f4ac9ecc9b5484a870826f687238240d48350bd7b1dadc99f5d88b6bf5710572542794a74c3332dc8fe15a3a0ba5d
|
7
|
+
data.tar.gz: 8d65071c1112f5df81cbb347060f9a416520809f4d794b99bab6a1cfee4bc2c532ff299fa2fb8a8bb793df942dd56253351336189772de7d0a674169693213ff
|
data/README.md
CHANGED
data/lib/vines.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Vines
|
4
4
|
class Cluster
|
5
|
-
# Subscribes to the redis nodes:all broadcast channel to listen for
|
5
|
+
# Subscribes to the redis `nodes:all` broadcast channel to listen for
|
6
6
|
# heartbeats from other cluster members. Also subscribes to a channel
|
7
7
|
# exclusively for this particular node, listening for stanzas routed to us
|
8
8
|
# from other nodes.
|
@@ -22,6 +22,8 @@ module Vines
|
|
22
22
|
# Create a new redis connection and subscribe to the nodes:all broadcast
|
23
23
|
# channel as well as the channel for this cluster node. Redis connections
|
24
24
|
# in subscribe mode cannot be used for other key/value operations.
|
25
|
+
#
|
26
|
+
# Returns nothing.
|
25
27
|
def subscribe
|
26
28
|
conn = @cluster.connect
|
27
29
|
conn.subscribe(ALL)
|
@@ -35,6 +37,8 @@ module Vines
|
|
35
37
|
|
36
38
|
# Recursively process incoming messages from the queue, guaranteeing they
|
37
39
|
# are processed in the order they are received.
|
40
|
+
#
|
41
|
+
# Returns nothing.
|
38
42
|
def process_messages
|
39
43
|
@messages.pop do |channel, message|
|
40
44
|
Fiber.new do
|
@@ -46,6 +50,11 @@ module Vines
|
|
46
50
|
|
47
51
|
# Process messages as they arrive on the pubsub channels to which we're
|
48
52
|
# subscribed.
|
53
|
+
#
|
54
|
+
# channel - The String channel name on which the message was received.
|
55
|
+
# message - The JSON formatted message String.
|
56
|
+
#
|
57
|
+
# Returns nothing.
|
49
58
|
def on_message(channel, message)
|
50
59
|
doc = JSON.parse(message)
|
51
60
|
case channel
|
@@ -56,9 +65,13 @@ module Vines
|
|
56
65
|
log.error("Cluster subscription message failed: #{e}")
|
57
66
|
end
|
58
67
|
|
59
|
-
# Process a message sent to the nodes:all broadcast channel. In the case
|
68
|
+
# Process a message sent to the `nodes:all` broadcast channel. In the case
|
60
69
|
# of node heartbeats, we update the last time we heard from this node so
|
61
70
|
# we can cleanup its session if it goes offline.
|
71
|
+
#
|
72
|
+
# message - The parsed Hash of received message data.
|
73
|
+
#
|
74
|
+
# Returns nothing.
|
62
75
|
def to_all(message)
|
63
76
|
case message[TYPE]
|
64
77
|
when ONLINE, HEARTBEAT
|
@@ -71,6 +84,10 @@ module Vines
|
|
71
84
|
# Process a message published to this node's channel. Messages sent to
|
72
85
|
# this channel are stanzas that need to be routed to connections attached
|
73
86
|
# to this node.
|
87
|
+
#
|
88
|
+
# message - The parsed Hash of received message data.
|
89
|
+
#
|
90
|
+
# Returns nothing.
|
74
91
|
def to_node(message)
|
75
92
|
case message[TYPE]
|
76
93
|
when STANZA then route_stanza(message)
|
@@ -80,6 +97,10 @@ module Vines
|
|
80
97
|
|
81
98
|
# Send the stanza, from a remote cluster node, to locally connected
|
82
99
|
# streams for the destination user.
|
100
|
+
#
|
101
|
+
# message - The parsed Hash of received message data.
|
102
|
+
#
|
103
|
+
# Returns nothing.
|
83
104
|
def route_stanza(message)
|
84
105
|
node = Nokogiri::XML(message[STANZA]).root rescue nil
|
85
106
|
return unless node
|
@@ -95,6 +116,10 @@ module Vines
|
|
95
116
|
|
96
117
|
# Update the roster information, that's cached in locally connected
|
97
118
|
# streams, for this user.
|
119
|
+
#
|
120
|
+
# message - The parsed Hash of received message data.
|
121
|
+
#
|
122
|
+
# Returns nothing.
|
98
123
|
def update_user(message)
|
99
124
|
jid = JID.new(message['jid']).bare
|
100
125
|
if user = @cluster.storage(jid.domain).find_user(jid)
|
data/lib/vines/storage.rb
CHANGED
@@ -10,6 +10,10 @@ module Vines
|
|
10
10
|
|
11
11
|
# Register a nickname that can be used in the config file to specify this
|
12
12
|
# storage implementation.
|
13
|
+
#
|
14
|
+
# name - The String name for this storage backend.
|
15
|
+
#
|
16
|
+
# Returns nothing.
|
13
17
|
def self.register(name)
|
14
18
|
@@nicks[name.to_sym] = self
|
15
19
|
end
|
@@ -25,15 +29,18 @@ module Vines
|
|
25
29
|
# with blocking IO don't need to worry about threading or blocking the
|
26
30
|
# EventMachine reactor thread if they wrap their methods with this one.
|
27
31
|
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
32
|
+
# Examples
|
33
|
+
#
|
34
|
+
# def find_user(jid)
|
35
|
+
# some_blocking_lookup(jid)
|
36
|
+
# end
|
37
|
+
# defer :find_user
|
33
38
|
#
|
34
39
|
# Storage classes that use asynchronous IO (through an EventMachine
|
35
40
|
# enabled library like em-http-request or em-redis) don't need any special
|
36
41
|
# consideration and must not use this method.
|
42
|
+
#
|
43
|
+
# Returns nothing.
|
37
44
|
def self.defer(method)
|
38
45
|
old = instance_method(method)
|
39
46
|
define_method method do |*args|
|
@@ -50,11 +57,14 @@ module Vines
|
|
50
57
|
# authenticate method as usual. This allows storage classes to implement
|
51
58
|
# their native authentication logic and not worry about handling LDAP.
|
52
59
|
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
60
|
+
# Examples
|
61
|
+
#
|
62
|
+
# def authenticate(username, password)
|
63
|
+
# some_user_lookup_by_password(username, password)
|
64
|
+
# end
|
65
|
+
# wrap_ldap :authenticate
|
66
|
+
#
|
67
|
+
# Returns nothing.
|
58
68
|
def self.wrap_ldap(method)
|
59
69
|
old = instance_method(method)
|
60
70
|
define_method method do |*args|
|
@@ -64,21 +74,24 @@ module Vines
|
|
64
74
|
|
65
75
|
# Wrap a method with Fiber yield and resume logic. The method must yield
|
66
76
|
# its result to a block. This makes it easier to write asynchronous
|
67
|
-
# implementations of
|
77
|
+
# implementations of `authenticate`, `find_user`, and `save_user` that
|
68
78
|
# block and return a result rather than yielding.
|
69
79
|
#
|
70
|
-
#
|
71
|
-
# def find_user(jid)
|
72
|
-
# http = EM::HttpRequest.new(url).get
|
73
|
-
# http.callback { yield build_user_from_http_response(http) }
|
74
|
-
# end
|
75
|
-
# fiber :find_user
|
80
|
+
# Examples
|
76
81
|
#
|
77
|
-
#
|
82
|
+
# def find_user(jid)
|
83
|
+
# http = EM::HttpRequest.new(url).get
|
84
|
+
# http.callback { yield build_user_from_http_response(http) }
|
85
|
+
# end
|
86
|
+
# fiber :find_user
|
87
|
+
#
|
88
|
+
# Because `find_user` has been wrapped in Fiber logic, we can call it
|
78
89
|
# synchronously even though it uses asynchronous EventMachine calls.
|
79
90
|
#
|
80
|
-
#
|
81
|
-
#
|
91
|
+
# user = storage.find_user('alice@wonderland.lit')
|
92
|
+
# puts user.nil?
|
93
|
+
#
|
94
|
+
# Returns nothing.
|
82
95
|
def self.fiber(method)
|
83
96
|
old = instance_method(method)
|
84
97
|
define_method method do |*args|
|
@@ -90,21 +103,26 @@ module Vines
|
|
90
103
|
end
|
91
104
|
end
|
92
105
|
|
93
|
-
# Return
|
106
|
+
# Return true if users are authenticated against an LDAP directory.
|
94
107
|
def ldap?
|
95
108
|
!!ldap
|
96
109
|
end
|
97
110
|
|
98
|
-
# Validate the username and password pair
|
99
|
-
#
|
111
|
+
# Validate the username and password pair.
|
112
|
+
#
|
113
|
+
# username - The String login JID to verify.
|
114
|
+
# password - The String password the user presented to the server.
|
100
115
|
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
116
|
+
# Examples
|
117
|
+
#
|
118
|
+
# user = storage.authenticate('alice@wonderland.lit', 'secr3t')
|
119
|
+
# puts user.nil?
|
104
120
|
#
|
105
121
|
# This default implementation validates the password against a bcrypt hash
|
106
122
|
# of the password stored in the database. Sub-classes not using bcrypt
|
107
123
|
# passwords must override this method.
|
124
|
+
#
|
125
|
+
# Returns a Vines::User object on success, nil on failure.
|
108
126
|
def authenticate(username, password)
|
109
127
|
user = find_user(username)
|
110
128
|
hash = BCrypt::Password.new(user.password) rescue nil
|
@@ -112,92 +130,141 @@ module Vines
|
|
112
130
|
end
|
113
131
|
wrap_ldap :authenticate
|
114
132
|
|
115
|
-
#
|
116
|
-
# could not be found. JID may be +nil+, a +String+, or a +Vines::JID+
|
117
|
-
# object. It may be a bare JID or a full JID. Implementations of this method
|
118
|
-
# must convert the JID to a bare JID before searching for the user in the
|
119
|
-
# database.
|
133
|
+
# Find the user in the storage database by their unique JID.
|
120
134
|
#
|
121
|
-
# user
|
122
|
-
#
|
135
|
+
# jid - The String or JID of the user, possibly nil. This may be either a
|
136
|
+
# bare JID or full JID. Implementations of this method must convert
|
137
|
+
# the JID to a bare JID before searching for the user in the database.
|
138
|
+
#
|
139
|
+
# Examples
|
140
|
+
#
|
141
|
+
# # Bare JID lookup.
|
142
|
+
# user = storage.find_user('alice@wonderland.lit')
|
143
|
+
# puts user.nil?
|
144
|
+
#
|
145
|
+
# # Full JID lookup.
|
146
|
+
# user = storage.find_user('alice@wonderland.lit/tea')
|
147
|
+
# puts user.nil?
|
148
|
+
#
|
149
|
+
# Returns the User identified by the JID, nil if not found.
|
123
150
|
def find_user(jid)
|
124
151
|
raise 'subclass must implement'
|
125
152
|
end
|
126
153
|
|
127
|
-
# Persist the
|
128
|
-
#
|
154
|
+
# Persist the user to the database, and return when the save is complete.
|
155
|
+
#
|
156
|
+
# user - The User to persist.
|
129
157
|
#
|
130
|
-
#
|
131
|
-
#
|
132
|
-
#
|
158
|
+
# Examples
|
159
|
+
#
|
160
|
+
# alice = Vines::User.new(jid: 'alice@wonderland.lit')
|
161
|
+
# storage.save_user(alice)
|
162
|
+
# puts 'saved'
|
163
|
+
#
|
164
|
+
# Returns nothing.
|
133
165
|
def save_user(user)
|
134
166
|
raise 'subclass must implement'
|
135
167
|
end
|
136
168
|
|
137
|
-
#
|
138
|
-
#
|
139
|
-
#
|
140
|
-
#
|
141
|
-
# vcard in the database.
|
169
|
+
# Find the user's vcard by their unique JID.
|
170
|
+
#
|
171
|
+
# jid - The String or JID of the user, possibly nil. This may be either a
|
172
|
+
# bare JID or full JID. Implementations of this method must convert
|
173
|
+
# the JID to a bare JID before searching for the vcard in the database.
|
174
|
+
#
|
175
|
+
# Examples
|
176
|
+
#
|
177
|
+
# card = storage.find_vcard('alice@wonderland.lit')
|
178
|
+
# puts card.nil?
|
142
179
|
#
|
143
|
-
#
|
144
|
-
# puts card.nil?
|
180
|
+
# Returns the vcard's Nokogiri::XML::Node, nil if not found.
|
145
181
|
def find_vcard(jid)
|
146
182
|
raise 'subclass must implement'
|
147
183
|
end
|
148
184
|
|
149
|
-
# Save the vcard to the database and return when the save is complete.
|
150
|
-
# may be a +String+ or a +Vines::JID+ object. It may be a bare JID or a
|
151
|
-
# full JID. Implementations of this method must convert the JID to a bare
|
152
|
-
# JID before saving the vcard. Card is a +Nokogiri::XML::Node+ object.
|
185
|
+
# Save the vcard to the database, and return when the save is complete.
|
153
186
|
#
|
154
|
-
#
|
155
|
-
#
|
156
|
-
#
|
187
|
+
# jid - The String or JID of the user, possibly nil. This may be either a
|
188
|
+
# bare JID or full JID. Implementations of this method must convert
|
189
|
+
# the JID to a bare JID before saving the vcard.
|
190
|
+
# card - The vcard's Nokogiri::XML::Node.
|
191
|
+
#
|
192
|
+
# Examples
|
193
|
+
#
|
194
|
+
# card = Nokogiri::XML('<vCard>...</vCard>').root
|
195
|
+
# storage.save_vcard('alice@wonderland.lit', card)
|
196
|
+
# puts 'saved'
|
197
|
+
#
|
198
|
+
# Returns nothing.
|
157
199
|
def save_vcard(jid, card)
|
158
200
|
raise 'subclass must implement'
|
159
201
|
end
|
160
202
|
|
161
|
-
#
|
162
|
-
#
|
163
|
-
# +String+, or a +Vines::JID+ object. It may be a bare JID or a full JID.
|
164
|
-
# Implementations of this method must convert the JID to a bare JID before
|
165
|
-
# searching for the fragment in the database.
|
166
|
-
#
|
167
|
-
# Private XML storage uniquely identifies fragments by JID, root element name,
|
203
|
+
# Find the private XML fragment previously stored by the user. Private
|
204
|
+
# XML storage uniquely identifies fragments by JID, root element name,
|
168
205
|
# and root element namespace.
|
169
206
|
#
|
170
|
-
#
|
171
|
-
#
|
172
|
-
#
|
207
|
+
# jid - The String or JID of the user, possibly nil. This may be either a
|
208
|
+
# bare JID or full JID. Implementations of this method must convert
|
209
|
+
# the JID to a bare JID before searching for the fragment in the database.
|
210
|
+
# node - The XML::Node that uniquely identifies the fragment by element
|
211
|
+
# name and namespace.
|
212
|
+
#
|
213
|
+
# Examples
|
214
|
+
#
|
215
|
+
# root = Nokogiri::XML('<custom xmlns="urn:custom:ns"/>').root
|
216
|
+
# fragment = storage.find_fragment('alice@wonderland.lit', root)
|
217
|
+
# puts fragment.nil?
|
218
|
+
#
|
219
|
+
# Returns the fragment's Nokogiri::XML::Node or nil if not found.
|
173
220
|
def find_fragment(jid, node)
|
174
221
|
raise 'subclass must implement'
|
175
222
|
end
|
176
223
|
|
177
|
-
# Save the XML fragment to the database and return when the save is complete.
|
178
|
-
#
|
179
|
-
#
|
180
|
-
# JID
|
224
|
+
# Save the XML fragment to the database, and return when the save is complete.
|
225
|
+
#
|
226
|
+
# jid - The String or JID of the user, possibly nil. This may be
|
227
|
+
# either a bare JID or full JID. Implementations of this method
|
228
|
+
# must convert the JID to a bare JID before searching for the
|
229
|
+
# fragment.
|
230
|
+
# fragment - The XML::Node to save.
|
231
|
+
#
|
232
|
+
# Examples
|
181
233
|
#
|
182
|
-
#
|
183
|
-
#
|
184
|
-
#
|
234
|
+
# fragment = Nokogiri::XML('<custom xmlns="urn:custom:ns">some data</custom>').root
|
235
|
+
# storage.save_fragment('alice@wonderland.lit', fragment)
|
236
|
+
# puts 'saved'
|
237
|
+
#
|
238
|
+
# Returns nothing.
|
185
239
|
def save_fragment(jid, fragment)
|
186
240
|
raise 'subclass must implement'
|
187
241
|
end
|
188
242
|
|
189
243
|
private
|
190
244
|
|
191
|
-
#
|
192
|
-
#
|
193
|
-
#
|
194
|
-
#
|
245
|
+
# Determine if any of the arguments are nil or empty strings.
|
246
|
+
#
|
247
|
+
# Examples
|
248
|
+
#
|
249
|
+
# username, password = 'alice@wonderland.lit', ''
|
250
|
+
# empty?(username, password) #=> true
|
251
|
+
#
|
252
|
+
# Returns true if any of the arguments are nil or empty strings.
|
195
253
|
def empty?(*args)
|
196
254
|
args.flatten.any? {|arg| (arg || '').strip.empty? }
|
197
255
|
end
|
198
256
|
|
199
|
-
#
|
200
|
-
# and logs any errors thrown by the provided block.
|
257
|
+
# Create a proc suitable for running on the EM.defer thread pool, that
|
258
|
+
# traps and logs any errors thrown by the provided block.
|
259
|
+
#
|
260
|
+
# block - The block to wrap in error handling.
|
261
|
+
#
|
262
|
+
# Examples
|
263
|
+
#
|
264
|
+
# op = operation { do_something_on_thread_pool() }
|
265
|
+
# EM.defer(op)
|
266
|
+
#
|
267
|
+
# Returns a Proc.
|
201
268
|
def operation
|
202
269
|
proc do
|
203
270
|
begin
|
@@ -209,10 +276,15 @@ module Vines
|
|
209
276
|
end
|
210
277
|
end
|
211
278
|
|
212
|
-
#
|
213
|
-
# using the username and password. Return +nil+ if authentication failed. If
|
279
|
+
# Bind to the LDAP server using the provided username and password. If
|
214
280
|
# authentication succeeds, but the user is not yet stored in our database,
|
215
281
|
# save the user to the database.
|
282
|
+
#
|
283
|
+
# username - The String JID to authenticate.
|
284
|
+
# password - The String password the user provided.
|
285
|
+
# block - The block that receives the authenticated User or nil.
|
286
|
+
#
|
287
|
+
# Returns the authenticated User or nil if authentication failed.
|
216
288
|
def authenticate_with_ldap(username, password, &block)
|
217
289
|
op = operation { ldap.authenticate(username, password) }
|
218
290
|
cb = proc {|user| save_ldap_user(user, &block) }
|
@@ -220,9 +292,14 @@ module Vines
|
|
220
292
|
end
|
221
293
|
fiber :authenticate_with_ldap
|
222
294
|
|
223
|
-
# Save missing users to the storage database after they're authenticated
|
224
|
-
# LDAP. This allows admins to define users once in LDAP and have them
|
225
|
-
# to the chat database the first time they successfully sign in.
|
295
|
+
# Save missing users to the storage database after they're authenticated
|
296
|
+
# with LDAP. This allows admins to define users once in LDAP and have them
|
297
|
+
# sync to the chat database the first time they successfully sign in.
|
298
|
+
#
|
299
|
+
# user - The User to persist, possibly nil.
|
300
|
+
# block - The block that receives the saved User, possibly nil.
|
301
|
+
#
|
302
|
+
# Returns nothing.
|
226
303
|
def save_ldap_user(user, &block)
|
227
304
|
Fiber.new do
|
228
305
|
if user.nil?
|