pubnub 0.1.12 → 3.3.0.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of pubnub might be problematic. Click here for more details.
- data/lib/pubnub.rb +298 -235
- data/lib/pubnub_crypto.rb +53 -142
- data/lib/pubnub_deferrable.rb +37 -0
- data/lib/pubnub_request.rb +287 -0
- metadata +90 -71
- data/README +0 -83
- data/examples/history_example.rb +0 -41
- data/examples/publish_example.rb +0 -69
- data/examples/subscribe_example.rb +0 -43
- data/examples/uuid_example.rb +0 -15
- data/tests/unit_test.rb +0 -88
data/lib/pubnub_crypto.rb
CHANGED
@@ -1,142 +1,53 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
params = {};
|
55
|
-
if cipher_Object.is_a? String
|
56
|
-
return decrypt(cipher_Object)
|
57
|
-
else
|
58
|
-
cipher_Object.each do |key,value|
|
59
|
-
case(key)
|
60
|
-
when(key)
|
61
|
-
params[key] = decrypt(value);
|
62
|
-
end
|
63
|
-
end
|
64
|
-
return params
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
#**
|
69
|
-
#* encrypt array
|
70
|
-
#*
|
71
|
-
#* @param message to encrypt (array)
|
72
|
-
#* @return cipher text array (encrypted array)
|
73
|
-
#*
|
74
|
-
def encryptArray(message)
|
75
|
-
params = []
|
76
|
-
i=0
|
77
|
-
message.each do |val|
|
78
|
-
case(val)
|
79
|
-
when(val)
|
80
|
-
params[i] = encrypt(val).chop.reverse.chop.reverse();
|
81
|
-
i = i+1
|
82
|
-
end
|
83
|
-
end
|
84
|
-
params = params.to_json
|
85
|
-
return params
|
86
|
-
end
|
87
|
-
|
88
|
-
#**
|
89
|
-
#* decrypt array
|
90
|
-
#*
|
91
|
-
#* @param cipher array (cipher text array to decrypt)
|
92
|
-
#* @return message decrypted (decrypted array)
|
93
|
-
#*
|
94
|
-
def decryptArray(message)
|
95
|
-
params = []
|
96
|
-
i=0
|
97
|
-
message.each do |val|
|
98
|
-
case(val)
|
99
|
-
when(val)
|
100
|
-
params[i] = decrypt(val);
|
101
|
-
i = i+1
|
102
|
-
end
|
103
|
-
end
|
104
|
-
return params
|
105
|
-
end
|
106
|
-
|
107
|
-
#**
|
108
|
-
#* encrypt plain text
|
109
|
-
#*
|
110
|
-
#* @param plain text (string to encrypt)
|
111
|
-
#* @return cipher text (encrypted text)
|
112
|
-
#*
|
113
|
-
def encrypt(message)
|
114
|
-
aes = OpenSSL::Cipher::Cipher.new(@@alg)
|
115
|
-
aes.encrypt
|
116
|
-
aes.key = @@key
|
117
|
-
aes.iv = @@iv
|
118
|
-
@@cipher = aes.update(message)
|
119
|
-
@@cipher << aes.final
|
120
|
-
@@ciphertext = [@@cipher].pack('m')
|
121
|
-
@@ciphertext = @@ciphertext.strip
|
122
|
-
@@ciphertext = @@ciphertext.gsub(/\n/,"")
|
123
|
-
@@ciphertext = '"' + @@ciphertext + '"'
|
124
|
-
return @@ciphertext
|
125
|
-
end
|
126
|
-
|
127
|
-
#**
|
128
|
-
#* decrypt plain text
|
129
|
-
#*
|
130
|
-
#* @param cipher text
|
131
|
-
#* @return plain text (decrypted text)
|
132
|
-
#*
|
133
|
-
def decrypt(cipher_text)
|
134
|
-
decode_cipher = OpenSSL::Cipher::Cipher.new(@@alg)
|
135
|
-
decode_cipher.decrypt
|
136
|
-
decode_cipher.key = @@key
|
137
|
-
decode_cipher.iv = @@iv
|
138
|
-
plain_text = decode_cipher.update(cipher_text.unpack('m')[0])
|
139
|
-
plain_text << decode_cipher.final
|
140
|
-
return plain_text
|
141
|
-
end
|
142
|
-
end
|
1
|
+
class PubnubCrypto
|
2
|
+
require 'yajl'
|
3
|
+
|
4
|
+
def initialize(cipher_key)
|
5
|
+
@alg = "AES-256-CBC"
|
6
|
+
sha256_key = Digest::SHA256.hexdigest(cipher_key)
|
7
|
+
@key = sha256_key.slice(0,32)
|
8
|
+
|
9
|
+
#puts("\nraw sha cipher_key is: #{cipher_key}")
|
10
|
+
#puts("raw sha cipher_key is: #{sha256_key}")
|
11
|
+
#puts("padded cipher_key is: #{@key}\n")
|
12
|
+
|
13
|
+
@iv = '0123456789012345'
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def encrypt(message)
|
18
|
+
|
19
|
+
aes = OpenSSL::Cipher::Cipher.new(@alg)
|
20
|
+
aes.encrypt
|
21
|
+
aes.key = @key
|
22
|
+
aes.iv = @iv
|
23
|
+
|
24
|
+
json_message = Yajl.dump(message)
|
25
|
+
cipher = aes.update(json_message)
|
26
|
+
cipher << aes.final
|
27
|
+
|
28
|
+
Base64.strict_encode64(cipher)
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def decrypt(cipher_text)
|
34
|
+
decode_cipher = OpenSSL::Cipher::Cipher.new(@alg)
|
35
|
+
decode_cipher.decrypt
|
36
|
+
decode_cipher.key = @key
|
37
|
+
decode_cipher.iv = @iv
|
38
|
+
|
39
|
+
plain_text = ""
|
40
|
+
|
41
|
+
begin
|
42
|
+
undecoded_text = Base64.decode64(cipher_text)
|
43
|
+
plain_text = decode_cipher.update(undecoded_text)
|
44
|
+
plain_text << decode_cipher.final
|
45
|
+
rescue => e
|
46
|
+
|
47
|
+
return "DECRYPTION_ERROR"
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
return Yajl.load(plain_text)
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
class PubnubDeferrable < EM::Protocols::HttpClient2
|
4
|
+
|
5
|
+
include EM::Deferrable
|
6
|
+
|
7
|
+
attr_accessor :start_time, :end_time, :elapsed_time, :pubnub_request
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def post_init
|
14
|
+
#puts("deferrable says my query is: #{pubnub_request.query}")
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def connection_completed
|
19
|
+
@start_time = Time.now
|
20
|
+
#puts("\n--- #{@start_time}: Connected with string: #{self.pubnub_request.query}")
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def unbind
|
25
|
+
@end_time = Time.now
|
26
|
+
|
27
|
+
if @start_time == nil
|
28
|
+
@start_time = Time.now
|
29
|
+
end
|
30
|
+
|
31
|
+
#puts("-- #{@end_time}: Disconnected.")
|
32
|
+
#puts("--- Elapsed connection time: #{@end_time - @start_time}s")
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,287 @@
|
|
1
|
+
class PubnubRequest
|
2
|
+
attr_accessor :cipher_key, :host, :query, :response, :timetoken, :url, :operation, :callback, :publish_key, :subscribe_key, :secret_key, :channel, :jsonp, :message, :ssl, :port
|
3
|
+
attr_accessor :history_limit, :history_count, :history_start, :history_end, :history_reverse, :session_uuid
|
4
|
+
|
5
|
+
class RequestError < RuntimeError;
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(args = {})
|
9
|
+
args = HashWithIndifferentAccess.new(args)
|
10
|
+
|
11
|
+
@operation = args[:operation].to_s
|
12
|
+
@callback = args[:callback]
|
13
|
+
@cipher_key = args[:cipher_key]
|
14
|
+
@session_uuid = args[:session_uuid]
|
15
|
+
@publish_key = args[:publish_key]
|
16
|
+
@subscribe_key = args[:subscribe_key]
|
17
|
+
@channel = args[:channel]
|
18
|
+
@jsonp = args[:jsonp].present? ? "1" : "0"
|
19
|
+
@message = args[:message]
|
20
|
+
@secret_key = args[:secret_key] || "0"
|
21
|
+
@timetoken = args[:timetoken] || "0"
|
22
|
+
@ssl = args[:ssl]
|
23
|
+
|
24
|
+
@port = args[:port]
|
25
|
+
@url = args[:url]
|
26
|
+
@host = args[:host]
|
27
|
+
@query = args[:query]
|
28
|
+
end
|
29
|
+
|
30
|
+
def op_exception
|
31
|
+
if @operation.present?
|
32
|
+
("Pubnub::" + @operation.to_s.capitalize + "Error").constantize
|
33
|
+
else
|
34
|
+
PubnubRequest::RequestError
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def ==(another)
|
39
|
+
self.operation == another.operation && self.callback == another.callback &&
|
40
|
+
self.channel == another.channel && self.message == another.message
|
41
|
+
end
|
42
|
+
|
43
|
+
def set_channel(options)
|
44
|
+
options = HashWithIndifferentAccess.new(options)
|
45
|
+
|
46
|
+
if options[:channel].blank?
|
47
|
+
raise(op_exception, "channel is a required parameter.")
|
48
|
+
else
|
49
|
+
self.channel = options[:channel].to_s
|
50
|
+
self
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def set_callback(options)
|
55
|
+
options = HashWithIndifferentAccess.new(options)
|
56
|
+
|
57
|
+
if options[:callback].blank?
|
58
|
+
raise(op_exception, "callback is a required parameter.")
|
59
|
+
elsif !options[:callback].try(:respond_to?, "call")
|
60
|
+
raise(op_exception, "callback is invalid.")
|
61
|
+
else
|
62
|
+
self.callback = options[:callback]
|
63
|
+
self
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
def set_cipher_key(options, self_cipher_key)
|
69
|
+
options = HashWithIndifferentAccess.new(options)
|
70
|
+
|
71
|
+
if self_cipher_key.present? && options['cipher_key'].present?
|
72
|
+
raise(op_exception, "existing cipher_key #{self_cipher_key} cannot be overridden at publish-time.")
|
73
|
+
|
74
|
+
elsif (self_cipher_key.present? && options[:cipher_key].blank?) || (self_cipher_key.blank? && options[:cipher_key].present?)
|
75
|
+
|
76
|
+
this_cipher_key = self_cipher_key || options[:cipher_key]
|
77
|
+
raise(Pubnub::PublishError, "secret key must be a string.") if this_cipher_key.class != String
|
78
|
+
self.cipher_key = this_cipher_key
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def set_secret_key(options, self_secret_key)
|
83
|
+
options = HashWithIndifferentAccess.new(options)
|
84
|
+
|
85
|
+
if self_secret_key.present? && options['secret_key'].present?
|
86
|
+
raise(Pubnub::PublishError, "existing secret_key #{self_secret_key} cannot be overridden at publish-time.")
|
87
|
+
|
88
|
+
elsif (self_secret_key.present? && options[:secret_key].blank?) || (self_secret_key.blank? && options[:secret_key].present?)
|
89
|
+
|
90
|
+
my_secret_key = self_secret_key || options[:secret_key]
|
91
|
+
raise(Pubnub::PublishError, "secret key must be a string.") if my_secret_key.class != String
|
92
|
+
|
93
|
+
signature = "{ @publish_key, @subscribe_key, @secret_key, channel, message}"
|
94
|
+
digest = OpenSSL::Digest.new("sha256")
|
95
|
+
key = [my_secret_key]
|
96
|
+
hmac = OpenSSL::HMAC.hexdigest(digest, key.pack("H*"), signature)
|
97
|
+
self.secret_key = hmac
|
98
|
+
else
|
99
|
+
self.secret_key = "0"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def set_message(options, self_cipher_key)
|
104
|
+
options = HashWithIndifferentAccess.new(options)
|
105
|
+
|
106
|
+
if options[:message].blank? && options[:message] != ""
|
107
|
+
raise(op_exception, "message is a required parameter.")
|
108
|
+
else
|
109
|
+
my_cipher_key = options[:cipher_key] || self_cipher_key
|
110
|
+
|
111
|
+
if my_cipher_key.present?
|
112
|
+
self.message = Yajl.dump(aes_encrypt(my_cipher_key, options, self))
|
113
|
+
else
|
114
|
+
self.message = Yajl.dump(options[:message])
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def set_publish_key(options, self_publish_key)
|
120
|
+
options = HashWithIndifferentAccess.new(options)
|
121
|
+
|
122
|
+
if options[:publish_key].blank? && self_publish_key.blank?
|
123
|
+
raise(Pubnub::PublishError, "publish_key is a required parameter.")
|
124
|
+
elsif self_publish_key.present? && options['publish_key'].present?
|
125
|
+
raise(Pubnub::PublishError, "existing publish_key #{self_publish_key} cannot be overridden at publish-time.")
|
126
|
+
else
|
127
|
+
self.publish_key = (self_publish_key || options[:publish_key]).to_s
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def set_subscribe_key(options, self_subscribe_key)
|
132
|
+
options = HashWithIndifferentAccess.new(options)
|
133
|
+
|
134
|
+
if options[:subscribe_key].blank? && self_subscribe_key.blank?
|
135
|
+
raise(op_exception, "subscribe_key is a required parameter.")
|
136
|
+
elsif self_subscribe_key.present? && options['subscribe_key'].present?
|
137
|
+
raise(op_exception, "existing subscribe_key #{self_subscribe_key} cannot be overridden at subscribe-time.")
|
138
|
+
else
|
139
|
+
self.subscribe_key = (self_subscribe_key || options[:subscribe_key]).to_s
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def package_response!(response_data)
|
144
|
+
self.response = response_data.respond_to?(:content) ? Yajl.load(response_data.content) : Yajl.load(response_data)
|
145
|
+
self.timetoken = self.response[1] unless self.operation == "time"
|
146
|
+
|
147
|
+
if self.cipher_key.present? && %w(subscribe history detailed_history).include?(self.operation)
|
148
|
+
|
149
|
+
myarr = Array.new
|
150
|
+
pc = PubnubCrypto.new(@cipher_key)
|
151
|
+
|
152
|
+
case @operation
|
153
|
+
when "publish"
|
154
|
+
iterate = self.response.first
|
155
|
+
when "subscribe"
|
156
|
+
iterate = self.response.first
|
157
|
+
when "history"
|
158
|
+
iterate = self.response
|
159
|
+
when "detailed_history"
|
160
|
+
iterate = self.response.first
|
161
|
+
|
162
|
+
else
|
163
|
+
raise(RequestError, "Don't know how to iterate on this operation.")
|
164
|
+
end
|
165
|
+
|
166
|
+
iterate.each do |message|
|
167
|
+
message = pc.decrypt(message)
|
168
|
+
myarr.push(message)
|
169
|
+
end
|
170
|
+
|
171
|
+
if %w(publish subscribe).include?(@operation)
|
172
|
+
self.response[0] = myarr
|
173
|
+
elsif @operation == "detailed_history"
|
174
|
+
json_response_data = Yajl.load(response_data)
|
175
|
+
self.response = [myarr, json_response_data[1], json_response_data[2]]
|
176
|
+
else
|
177
|
+
self.response = myarr
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def format_url!(override_timetoken = nil)
|
184
|
+
|
185
|
+
raise(Pubnub::PublishError, "Missing .operation in PubnubRequest object") if self.operation.blank?
|
186
|
+
|
187
|
+
if @ssl.present?
|
188
|
+
origin = 'https://' + Pubnub::ORIGIN_HOST
|
189
|
+
@port = 443
|
190
|
+
else
|
191
|
+
origin = 'http://' + Pubnub::ORIGIN_HOST
|
192
|
+
@port = 80
|
193
|
+
end
|
194
|
+
|
195
|
+
if override_timetoken.present?
|
196
|
+
self.timetoken = override_timetoken.to_s
|
197
|
+
end
|
198
|
+
|
199
|
+
case self.operation.to_s
|
200
|
+
when "publish"
|
201
|
+
url_array = [self.operation.to_s, self.publish_key.to_s, self.subscribe_key.to_s,
|
202
|
+
self.secret_key.to_s, self.channel.to_s, "0", self.message]
|
203
|
+
|
204
|
+
when "subscribe"
|
205
|
+
url_array = [self.operation.to_s, self.subscribe_key.to_s, self.channel.to_s, "0", @timetoken]
|
206
|
+
|
207
|
+
when "presence"
|
208
|
+
url_array = ["subscribe", self.subscribe_key.to_s, ((self.channel.to_s) + "-pnpres"), "0", @timetoken]
|
209
|
+
|
210
|
+
when "time"
|
211
|
+
url_array = [self.operation.to_s, "0"]
|
212
|
+
|
213
|
+
when "history"
|
214
|
+
url_array = [self.operation.to_s, self.subscribe_key.to_s, self.channel.to_s, "0", self.history_limit.to_s]
|
215
|
+
|
216
|
+
when "detailed_history"
|
217
|
+
url_array = ["v2", "history", "sub-key", self.subscribe_key.to_s, "channel", self.channel.to_s]
|
218
|
+
|
219
|
+
when "here_now"
|
220
|
+
url_array = ["v2", "presence", "sub-key", self.subscribe_key.to_s, "channel", self.channel.to_s]
|
221
|
+
|
222
|
+
else
|
223
|
+
|
224
|
+
raise(PubnubRequest::RequestError, "I can't create that URL for you due to unknown operation type.")
|
225
|
+
end
|
226
|
+
|
227
|
+
self.url = origin + encode_URL(url_array)
|
228
|
+
|
229
|
+
uri = URI.parse(self.url)
|
230
|
+
|
231
|
+
self.host = uri.host
|
232
|
+
url_params = ""
|
233
|
+
|
234
|
+
if %w(subscribe presence).include?(@operation)
|
235
|
+
uri.query = uri.query.blank? ? "uuid=#{@session_uuid}" : (uri.query + "uuid=#{@session_uuid}")
|
236
|
+
|
237
|
+
elsif @operation == "detailed_history"
|
238
|
+
url_sep = "?"
|
239
|
+
|
240
|
+
if @history_count || @history_start || @history_end || @history_reverse
|
241
|
+
|
242
|
+
if @history_count
|
243
|
+
url_params += url_sep + "count=" + @history_count.to_s
|
244
|
+
url_sep = "&"
|
245
|
+
end
|
246
|
+
|
247
|
+
if @history_start
|
248
|
+
url_params += url_sep + "start=" + @history_start.to_s
|
249
|
+
url_sep = "&"
|
250
|
+
end
|
251
|
+
|
252
|
+
if @history_end
|
253
|
+
url_params += url_sep + "end=" + @history_end.to_s
|
254
|
+
url_sep = "&"
|
255
|
+
end
|
256
|
+
|
257
|
+
if @history_reverse
|
258
|
+
url_params += url_sep + "reverse=true"
|
259
|
+
url_sep = "&"
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
self.query = uri.path + (uri.query.present? ? ("?" + uri.query) : "") + url_params
|
265
|
+
self.url += url_params
|
266
|
+
self
|
267
|
+
|
268
|
+
end
|
269
|
+
|
270
|
+
def aes_encrypt(cipher_key, options, publish_request)
|
271
|
+
options = HashWithIndifferentAccess.new(options)
|
272
|
+
|
273
|
+
pc = PubnubCrypto.new(cipher_key)
|
274
|
+
publish_request.message = pc.encrypt(options[:message])
|
275
|
+
|
276
|
+
end
|
277
|
+
|
278
|
+
def encode_URL(request)
|
279
|
+
## Construct Request URL
|
280
|
+
url = '/' + request.map { |bit| bit.split('').map { |ch|
|
281
|
+
' ~`!@#$%^&*()+=[]\\{}|;\':",./<>?'.index(ch) ?
|
282
|
+
'%' + ch.unpack('H2')[0].to_s.upcase : URI.encode(ch)
|
283
|
+
}.join('') }.join('/')
|
284
|
+
return url
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|