vines 0.4.9 → 0.4.10
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 +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?
|