.rubocop.yml 🔗
@@ -49,6 +49,9 @@ Style/AlignParameters:
Style/BlockDelimiters:
Enabled: false
+Style/CaseIndentation:
+ EnforcedStyle: end
+
Style/Documentation:
Enabled: false
Denver Gingerich created
In particular, tested SMS send/receive (shouldn't have been impacted)
as well as the registration form display on an already-registered
account (using Cheogram's "Configure direct message route" ad-hoc
command). Both appeared to work fine. Since we don't currently use
much more than this in JMP (most registration is done behind the
scenes and Cheogram mainly uses registration for verification reasons)
that should be sufficient testing for now.
See merge request !8 for the discussion and details behind the merge.
.rubocop.yml | 3
Gemfile | 5
sgx-catapult.rb | 477 +++++++++++++++++++++++---------------------------
3 files changed, 221 insertions(+), 264 deletions(-)
@@ -49,6 +49,9 @@ Style/AlignParameters:
Style/BlockDelimiters:
Enabled: false
+Style/CaseIndentation:
+ EnforcedStyle: end
+
Style/Documentation:
Enabled: false
@@ -2,14 +2,11 @@ source 'https://rubygems.org'
gem 'activesupport', '<5.0.0'
gem 'blather'
+gem 'em-hiredis'
gem 'em-http-request'
gem 'eventmachine', '1.0.1'
gem 'promise.rb'
-gem 'em-hiredis'
-gem 'hiredis', '~> 0.6.0'
-gem 'redis', '>= 3.2.0'
-
gem 'goliath'
gem 'log4r'
@@ -22,8 +22,6 @@ require 'blather/client/dsl'
require 'em-hiredis'
require 'em-http-request'
require 'json'
-require 'net/http'
-require 'redis/connection/hiredis'
require 'securerandom'
require 'time'
require 'uri'
@@ -114,7 +112,8 @@ module SGXcatapult
return orig
end
- setup ARGV[0], ARGV[1], ARGV[2], ARGV[3]
+ # workqueue_count MUST be 0 or else Blather uses threads!
+ setup ARGV[0], ARGV[1], ARGV[2], ARGV[3], nil, nil, workqueue_count: 0
def self.pass_on_message(m, users_num, jid)
# setup delivery receipt; similar to a reply
@@ -143,7 +142,9 @@ module SGXcatapult
write_to_stream rcpt
end
- def self.call_catapult(token, secret, m, pth, body, head={}, code=[200])
+ def self.call_catapult(
+ token, secret, m, pth, body=nil, head={}, code=[200]
+ )
EM::HttpRequest.new(
"https://api.catapult.inetwork.com/#{pth}"
).public_send(
@@ -159,10 +160,7 @@ module SGXcatapult
if code.include?(http.response_header.status)
http.response
else
- # TODO: add text; mention code number
- EMPromise.reject(
- [:cancel, 'internal-server-error']
- )
+ EMPromise.reject(http.response_header.status)
end
}
end
@@ -198,7 +196,12 @@ module SGXcatapult
)),
{'Content-Type' => 'application/json'},
[201]
- )
+ ).catch {
+ # TODO: add text; mention code number
+ EMPromise.reject(
+ [:cancel, 'internal-server-error']
+ )
+ }
end
def self.validate_num(num)
@@ -462,7 +465,11 @@ module SGXcatapult
:put,
path,
@partial_data[cn[0]['sid']]
- ),
+ ).catch {
+ EMPromise.reject([
+ :cancel, 'internal-server-error'
+ ])
+ },
to_catapult(
i,
"https://api.catapult.inetwork.com/" +
@@ -537,285 +544,235 @@ module SGXcatapult
write_to_stream msg
end
- def self.check_then_register(user_id, api_token, api_secret, phone_num,
- i, qn)
-
- jid_key = "catapult_jid-" + phone_num
-
- bare_jid = i.from.to_s.split('/', 2)[0]
- cred_key = "catapult_cred-" + bare_jid
-
- # TODO: pre-validate ARGV[5] is integer
- conn = Hiredis::Connection.new
- conn.connect(ARGV[4], ARGV[5].to_i)
-
- conn.write ["GET", jid_key]
- existing_jid = conn.read
-
- if not existing_jid.nil? and existing_jid != bare_jid
- conn.disconnect
-
- # TODO: add/log text re credentials exist already
- write_to_stream error_msg(
- i.reply, qn, :cancel,
- 'conflict')
- return false
- end
-
- # ASSERT: existing_jid is nil or equal to bare_jid
-
- conn.write ["EXISTS", cred_key]
- creds_exist = conn.read
- if 1 == creds_exist
- conn.write ["LRANGE", cred_key, 0, 3]
- if [user_id, api_token, api_secret, phone_num] !=
- conn.read
+ def self.check_then_register(i, *creds)
+ jid_key = "catapult_jid-#{creds.last}"
+ bare_jid = i.from.stripped
+ cred_key = "catapult_cred-#{bare_jid}"
- conn.disconnect
-
- # TODO: add/log txt re credentials exist already
- write_to_stream error_msg(
- i.reply, qn, :cancel,
- 'conflict')
- return false
+ REDIS.get(jid_key).then { |existing_jid|
+ if existing_jid && existing_jid != bare_jid
+ # TODO: add/log text: credentials exist already
+ EMPromise.reject([:cancel, 'conflict'])
end
- end
-
- # ASSERT: cred_key does not exist or its value equals input vals
-
- # not necessary if existing_jid non-nil, but easier to do anyway
- conn.write ["SET", jid_key, bare_jid]
- if conn.read != 'OK'
- conn.disconnect
-
- # TODO: catch/relay RuntimeError
- # TODO: add txt re push failure
- write_to_stream error_msg(
- i.reply, qn, :cancel,
- 'internal-server-error')
- return false
- end
-
- if 1 == creds_exist
- # per above ASSERT, cred_key value equals input already
- conn.disconnect
+ }.then {
+ REDIS.lrange(cred_key, 0, 3)
+ }.then { |existing_creds|
+ # TODO: add/log text: credentials exist already
+ if existing_creds.length == 4 && creds != existing_creds
+ EMPromise.reject([:cancel, 'conflict'])
+ elsif existing_creds.length < 4
+ REDIS.rpush(cred_key, *creds).then { |length|
+ if length != 4
+ EMPromise.reject([
+ :cancel,
+ 'internal-server-error'
+ ])
+ end
+ }
+ end
+ }.then {
+ # not necessary if existing_jid non-nil, easier this way
+ REDIS.set(jid_key, bare_jid)
+ }.then { |result|
+ if result != 'OK'
+ # TODO: add txt re push failure
+ EMPromise.reject(
+ [:cancel, 'internal-server-error']
+ )
+ end
+ }.then {
write_to_stream i.reply
- return true
- end
-
- conn.write ["RPUSH", cred_key, user_id]
- conn.write ["RPUSH", cred_key, api_token]
- conn.write ["RPUSH", cred_key, api_secret]
- conn.write ["RPUSH", cred_key, phone_num]
+ }
+ end
- # TODO: confirm cred_key list size == 4
+ def self.creds_from_registration_query(qn)
+ xn = qn.children.find { |v| v.element_name == "x" }
- (1..4).each do |n|
- # TODO: catch/relay RuntimeError
- result = conn.read
- if result != n
- conn.disconnect
+ if xn
+ xn.children.each_with_object({}) do |field, h|
+ next if field.element_name != "field"
+ val = field.children.find { |v|
+ v.element_name == "value"
+ }
- write_to_stream error_msg(
- i.reply, qn, :cancel,
- 'internal-server-error')
- return false
+ case field['var']
+ when 'nick'
+ h[:user_id] = val.text
+ when 'username'
+ h[:api_token] = val.text
+ when 'password'
+ h[:api_secret] = val.text
+ when 'phone'
+ h[:phone_num] = val.text
+ else
+ # TODO: error
+ puts "?: #{field['var']}"
+ end
end
- end
- conn.disconnect
-
- write_to_stream i.reply
-
- return true
+ else
+ qn.children.each_with_object({}) do |field, h|
+ case field.element_name
+ when "nick"
+ h[:user_id] = field.text
+ when "username"
+ h[:api_token] = field.text
+ when "password"
+ h[:api_secret] = field.text
+ when "phone"
+ h[:phone_num] = field.text
+ end
+ end
+ end.values_at(:user_id, :api_token, :api_secret, :phone_num)
end
- iq '/iq/ns:query', ns: 'jabber:iq:register' do |i, qn|
- puts "IQ: #{i.inspect}"
-
- if i.type == :set
- rn = qn.children.find { |v| v.element_name == "remove" }
- if not rn.nil?
+ def self.process_registration(i, qn)
+ EMPromise.resolve(
+ qn.children.find { |v| v.element_name == "remove" }
+ ).then { |rn|
+ if rn
puts "received <remove/> - ignoring for now..."
- next
- end
-
- xn = qn.children.find { |v| v.element_name == "x" }
-
- user_id = ''
- api_token = ''
- api_secret = ''
- phone_num = ''
-
- if xn.nil?
- user_id = qn.children.find { |v|
- v.element_name == "nick"
- }
- api_token = qn.children.find { |v|
- v.element_name == "username"
- }
- api_secret = qn.children.find { |v|
- v.element_name == "password"
- }
- phone_num = qn.children.find { |v|
- v.element_name == "phone"
- }
+ EMPromise.reject(:done)
else
- xn.children.each do |field|
- if field.element_name == "field"
- val = field.children.find { |v|
- v.element_name == "value"
- }
-
- case field['var']
- when 'nick'
- user_id = val.text
- when 'username'
- api_token = val.text
- when 'password'
- api_secret = val.text
- when 'phone'
- phone_num = val.text
- else
- # TODO: error
- puts "?: " +field['var']
- end
- end
- end
+ creds_from_registration_query(qn)
end
-
- if phone_num[0] != '+'
+ }.then { |user_id, api_token, api_secret, phone_num|
+ if phone_num[0] == '+'
+ [user_id, api_token, api_secret, phone_num]
+ else
# TODO: add text re number not (yet) supported
- write_to_stream error_msg(
- i.reply, qn, :cancel,
- 'item-not-found'
- )
- next
+ EMPromise.reject([:cancel, 'item-not-found'])
end
-
- uri = URI.parse('https://api.catapult.inetwork.com')
- http = Net::HTTP.new(uri.host, uri.port)
- http.use_ssl = true
- request = Net::HTTP::Get.new('/v1/users/' + user_id +
- '/phoneNumbers/' + phone_num)
- request.basic_auth api_token, api_secret
- response = http.request(request)
-
- puts 'API response: ' + response.to_s + ' with code ' +
- response.code + ', body "' + response.body + '"'
-
- if response.code == '200'
- params = JSON.parse response.body
+ }.then { |user_id, api_token, api_secret, phone_num|
+ call_catapult(
+ api_token,
+ api_secret,
+ :get,
+ "v1/users/#{user_id}/phoneNumbers/#{phone_num}"
+ ).then { |response|
+ params = JSON.parse(response)
if params['numberState'] == 'enabled'
- if not check_then_register(
- user_id, api_token, api_secret,
- phone_num, i, qn
+ check_then_register(
+ i,
+ user_id,
+ api_token,
+ api_secret,
+ phone_num
)
- next
- end
else
# TODO: add text re number disabled
- write_to_stream error_msg(
- i.reply, qn,
- :modify, 'not-acceptable'
- )
+ EMPromise.reject([:modify, 'not-acceptable'])
end
- elsif response.code == '401'
+ }
+ }.catch { |e|
+ EMPromise.reject(case e
+ when 401
# TODO: add text re bad credentials
- write_to_stream error_msg(
- i.reply, qn, :auth,
- 'not-authorized'
- )
- elsif response.code == '404'
+ [:auth, 'not-authorized']
+ when 404
# TODO: add text re number not found or disabled
- write_to_stream error_msg(
- i.reply, qn, :cancel,
- 'item-not-found'
- )
+ [:cancel, 'item-not-found']
+ when Integer
+ [:modify, 'not-acceptable']
else
- # TODO: add text re misc error, and mention code
- write_to_stream error_msg(
- i.reply, qn, :modify,
- 'not-acceptable'
+ e
+ end)
+ }
+ end
+
+ def self.registration_form(orig, existing_number=nil)
+ msg = Nokogiri::XML::Node.new 'query', orig.document
+ msg['xmlns'] = 'jabber:iq:register'
+
+ if existing_number
+ msg.add_child(
+ Nokogiri::XML::Node.new(
+ 'registered', msg.document
)
- end
+ )
+ end
- elsif i.type == :get
- orig = i.reply
+ n1 = Nokogiri::XML::Node.new(
+ 'instructions', msg.document
+ )
+ n1.content = "Enter the information from your Account "\
+ "page as well as the Phone Number\nin your "\
+ "account you want to use (ie. '+12345678901')"\
+ ".\nUser Id is nick, API Token is username, "\
+ "API Secret is password, Phone Number is phone"\
+ ".\n\nThe source code for this gateway is at "\
+ "https://gitlab.com/ossguy/sgx-catapult ."\
+ "\nCopyright (C) 2017 Denver Gingerich and "\
+ "others, licensed under AGPLv3+."
+ n2 = Nokogiri::XML::Node.new 'nick', msg.document
+ n3 = Nokogiri::XML::Node.new 'username', msg.document
+ n4 = Nokogiri::XML::Node.new 'password', msg.document
+ n5 = Nokogiri::XML::Node.new 'phone', msg.document
+ n5.content = existing_number.to_s
+ msg.add_child(n1)
+ msg.add_child(n2)
+ msg.add_child(n3)
+ msg.add_child(n4)
+ msg.add_child(n5)
+
+ x = Blather::Stanza::X.new :form, [
+ {
+ required: true, type: :"text-single",
+ label: 'User Id', var: 'nick'
+ },
+ {
+ required: true, type: :"text-single",
+ label: 'API Token', var: 'username'
+ },
+ {
+ required: true, type: :"text-private",
+ label: 'API Secret', var: 'password'
+ },
+ {
+ required: true, type: :"text-single",
+ label: 'Phone Number', var: 'phone',
+ value: existing_number.to_s
+ }
+ ]
+ x.title = 'Register for '\
+ 'Soprani.ca Gateway to XMPP - Catapult'
+ x.instructions = "Enter the details from your Account "\
+ "page as well as the Phone Number\nin your "\
+ "account you want to use (ie. '+12345678901')"\
+ ".\n\nThe source code for this gateway is at "\
+ "https://gitlab.com/ossguy/sgx-catapult ."\
+ "\nCopyright (C) 2017 Denver Gingerich and "\
+ "others, licensed under AGPLv3+."
+ msg.add_child(x)
- bare_jid = i.from.to_s.split('/', 2)[0]
- cred_key = "catapult_cred-" + bare_jid
+ orig.add_child(msg)
- conn = Hiredis::Connection.new
- conn.connect(ARGV[4], ARGV[5].to_i)
- conn.write(["LINDEX", cred_key, 3])
- existing_number = conn.read
- conn.disconnect
+ return orig
+ end
- msg = Nokogiri::XML::Node.new 'query', orig.document
- msg['xmlns'] = 'jabber:iq:register'
+ iq '/iq/ns:query', ns: 'jabber:iq:register' do |i, qn|
+ puts "IQ: #{i.inspect}"
- if existing_number
- msg.add_child(
- Nokogiri::XML::Node.new('registered', msg.document)
- )
+ case i.type
+ when :set
+ process_registration(i, qn)
+ when :get
+ bare_jid = i.from.stripped
+ cred_key = "catapult_cred-#{bare_jid}"
+ REDIS.lindex(cred_key, 3).then { |existing_number|
+ reply = registration_form(i.reply, existing_number)
+ puts "RESPONSE2: #{reply.inspect}"
+ write_to_stream reply
+ }
+ else
+ # Unknown IQ, ignore for now
+ EMPromise.reject(:done)
+ end.catch { |e|
+ if e.is_a?(Array) && e.length == 2
+ write_to_stream error_msg(i.reply, qn, *e)
+ elsif e != :done
+ EMPromise.reject(e)
end
-
- n1 = Nokogiri::XML::Node.new 'instructions', msg.document
- n1.content= "Enter the information from your Account "\
- "page as well as the Phone Number\nin your "\
- "account you want to use (ie. '+12345678901')"\
- ".\nUser Id is nick, API Token is username, "\
- "API Secret is password, Phone Number is phone"\
- ".\n\nThe source code for this gateway is at "\
- "https://gitlab.com/ossguy/sgx-catapult ."\
- "\nCopyright (C) 2017 Denver Gingerich and "\
- "others, licensed under AGPLv3+."
- n2 = Nokogiri::XML::Node.new 'nick', msg.document
- n3 = Nokogiri::XML::Node.new 'username', msg.document
- n4 = Nokogiri::XML::Node.new 'password', msg.document
- n5 = Nokogiri::XML::Node.new 'phone', msg.document
- n5.content = existing_number.to_s
- msg.add_child(n1)
- msg.add_child(n2)
- msg.add_child(n3)
- msg.add_child(n4)
- msg.add_child(n5)
-
- x = Blather::Stanza::X.new :form, [
- {
- required: true, type: :"text-single",
- label: 'User Id', var: 'nick'
- },
- {
- required: true, type: :"text-single",
- label: 'API Token', var: 'username'
- },
- {
- required: true, type: :"text-private",
- label: 'API Secret', var: 'password'
- },
- {
- required: true, type: :"text-single",
- label: 'Phone Number', var: 'phone',
- value: existing_number.to_s
- }
- ]
- x.title= 'Register for '\
- 'Soprani.ca Gateway to XMPP - Catapult'
- x.instructions= "Enter the details from your Account "\
- "page as well as the Phone Number\nin your "\
- "account you want to use (ie. '+12345678901')"\
- ".\n\nThe source code for this gateway is at "\
- "https://gitlab.com/ossguy/sgx-catapult ."\
- "\nCopyright (C) 2017 Denver Gingerich and "\
- "others, licensed under AGPLv3+."
- msg.add_child(x)
-
- orig.add_child(msg)
- puts "RESPONSE2: #{orig.inspect}"
- write_to_stream orig
- puts "SENT"
- end
+ }.catch(&method(:panic))
end
subscription(:request?) do |s|