diff --git a/app/jobs/xmpp_exchange_contacts_job.rb b/app/jobs/xmpp_exchange_contacts_job.rb index 0de829c..269604d 100644 --- a/app/jobs/xmpp_exchange_contacts_job.rb +++ b/app/jobs/xmpp_exchange_contacts_job.rb @@ -2,21 +2,6 @@ class XmppExchangeContactsJob < ApplicationJob queue_as :default def perform(inviter, invitee) - return unless inviter.service_enabled?(:ejabberd) && - invitee.service_enabled?(:ejabberd) && - inviter.preferences[:xmpp_exchange_contacts_with_invitees] - - ejabberd = EjabberdApiClient.new - - ejabberd.add_rosteritem({ - "localuser": invitee.cn, "localhost": invitee.ou, - "user": inviter.cn, "host": inviter.ou, - "nick": inviter.cn, "group": Setting.ejabberd_buddy_roster, "subs": "both" - }) - ejabberd.add_rosteritem({ - "localuser": inviter.cn, "localhost": inviter.ou, - "user": invitee.cn, "host": invitee.ou, - "nick": invitee.cn, "group": Setting.ejabberd_buddy_roster, "subs": "both" - }) + EjabberdManager::ExchangeContacts.call(inviter:, invitee:) end end diff --git a/app/jobs/xmpp_send_message_job.rb b/app/jobs/xmpp_send_message_job.rb index e8eebf5..37a7e57 100644 --- a/app/jobs/xmpp_send_message_job.rb +++ b/app/jobs/xmpp_send_message_job.rb @@ -2,7 +2,6 @@ class XmppSendMessageJob < ApplicationJob queue_as :default def perform(payload) - ejabberd = EjabberdApiClient.new - ejabberd.send_message payload + EjabberdManager::SendMessage.call(payload:) end end diff --git a/app/jobs/xmpp_set_avatar_job.rb b/app/jobs/xmpp_set_avatar_job.rb index 642225c..e1b89d7 100644 --- a/app/jobs/xmpp_set_avatar_job.rb +++ b/app/jobs/xmpp_set_avatar_job.rb @@ -1,97 +1,7 @@ -require 'digest' -require "image_processing/vips" - class XmppSetAvatarJob < ApplicationJob queue_as :default def perform(user:, overwrite: false) - return if Rails.env.development? - @user = user - - unless overwrite - current_avatar = get_current_avatar - Rails.logger.info { "User #{user.cn} already has an avatar set" } - return if current_avatar.present? - end - - Rails.logger.debug { "Setting XMPP avatar for user #{user.cn}" } - - stanzas = build_xep0084_stanzas - - stanzas.each do |stanza| - payload = { from: @user.address, to: @user.address, stanza: stanza } - res = ejabberd.send_stanza payload - raise res.inspect if res.status != 200 - end + EjabberdManager::SetAvatar.call(user:, overwrite:) end - - private - - def ejabberd - @ejabberd ||= EjabberdApiClient.new - end - - def get_current_avatar - res = ejabberd.get_vcard2 @user, "PHOTO", "BINVAL" - - if res.status == 200 - # VCARD PHOTO/BINVAL prop exists - res.body - elsif res.status == 400 - # VCARD or PHOTO/BINVAL prop does not exist - nil - else - # Unexpected error, let job fail - raise res.inspect - end - end - - def process_avatar - @user.avatar.blob.open do |file| - processed = ImageProcessing::Vips - .source(file) - .resize_to_fill(256, 256) - .convert("png") - .call - processed.read - end - end - - # See https://xmpp.org/extensions/xep-0084.html - def build_xep0084_stanzas - img_data = process_avatar - sha1_hash = Digest::SHA1.hexdigest(img_data) - base64_data = Base64.strict_encode64(img_data) - - [ - """ - - - - - #{base64_data} - - - - - """.strip, - """ - - - - - - - - - - - - """.strip, - ] - end end diff --git a/app/jobs/xmpp_set_default_bookmarks_job.rb b/app/jobs/xmpp_set_default_bookmarks_job.rb index 92cd8a5..e9539c9 100644 --- a/app/jobs/xmpp_set_default_bookmarks_job.rb +++ b/app/jobs/xmpp_set_default_bookmarks_job.rb @@ -2,25 +2,6 @@ class XmppSetDefaultBookmarksJob < ApplicationJob queue_as :default def perform(user) - return unless Setting.xmpp_default_rooms.any? - @user = user - ejabberd = EjabberdApiClient.new - ejabberd.private_set user, storage_content - end - - def storage_content - bookmarks = "" - Setting.xmpp_default_rooms.each do |r| - bookmarks << conference_element( - jid: r[/<(.+)>/, 1], name: r[/^(.+)\s/, 1], nick: @user.cn, - autojoin: Setting.xmpp_autojoin_default_rooms - ) - end - - "#{bookmarks}" - end - - def conference_element(jid:, name:, autojoin: false, nick:) - "#{nick}" + EjabberdManager::SetDefaultBookmarks.call(user:) end end diff --git a/app/services/ejabberd_manager/exchange_contacts.rb b/app/services/ejabberd_manager/exchange_contacts.rb new file mode 100644 index 0000000..f71cddc --- /dev/null +++ b/app/services/ejabberd_manager/exchange_contacts.rb @@ -0,0 +1,25 @@ +module EjabberdManager + class ExchangeContacts < EjabberdManagerService + def initialize(inviter:, invitee:) + @inviter = inviter + @invitee = invitee + end + + def call + return unless @inviter.service_enabled?(:ejabberd) && + @invitee.service_enabled?(:ejabberd) && + @inviter.preferences[:xmpp_exchange_contacts_with_invitees] + + add_rosteritem({ + "localuser": @invitee.cn, "localhost": @invitee.ou, + "user": @inviter.cn, "host": @inviter.ou, + "nick": @inviter.cn, "group": Setting.ejabberd_buddy_roster, "subs": "both" + }) + add_rosteritem({ + "localuser": @inviter.cn, "localhost": @inviter.ou, + "user": @invitee.cn, "host": @invitee.ou, + "nick": @invitee.cn, "group": Setting.ejabberd_buddy_roster, "subs": "both" + }) + end + end +end diff --git a/app/services/ejabberd_manager/get_avatar.rb b/app/services/ejabberd_manager/get_avatar.rb new file mode 100644 index 0000000..8569fb7 --- /dev/null +++ b/app/services/ejabberd_manager/get_avatar.rb @@ -0,0 +1,25 @@ +module EjabberdManager + class GetAvatar < EjabberdManagerService + def initialize(user:) + @user = user + end + + def call + res = get_vcard2 @user, "PHOTO", "BINVAL" + + if res.status == 200 + # VCARD PHOTO/BINVAL prop exists + img_base64 = JSON.parse(res.body)["content"] + ct_res = get_vcard2 @user, "PHOTO", "TYPE" + content_type = JSON.parse(ct_res.body)["content"] + { content_type:, img_base64: } + elsif res.status == 400 + # VCARD or PHOTO/BINVAL prop does not exist + nil + else + # Unexpected error, let job fail + raise res.inspect + end + end + end +end diff --git a/app/services/ejabberd_manager/send_message.rb b/app/services/ejabberd_manager/send_message.rb new file mode 100644 index 0000000..86d6a9d --- /dev/null +++ b/app/services/ejabberd_manager/send_message.rb @@ -0,0 +1,11 @@ +module EjabberdManager + class SendMessage < EjabberdManagerService + def initialize(payload:) + @payload = payload + end + + def call + send_message @payload + end + end +end diff --git a/app/services/ejabberd_manager/set_avatar.rb b/app/services/ejabberd_manager/set_avatar.rb new file mode 100644 index 0000000..fcff840 --- /dev/null +++ b/app/services/ejabberd_manager/set_avatar.rb @@ -0,0 +1,80 @@ +require 'digest' +require "image_processing/vips" + +module EjabberdManager + class SetAvatar < EjabberdManagerService + def initialize(user:, overwrite: false) + @user = user + @overwrite = overwrite + end + + def call + unless @overwrite + current_avatar = EjabberdManager::GetAvatar.call(user: @user) + Rails.logger.info { "User #{user.cn} already has an avatar set" } + return if current_avatar.present? + end + + Rails.logger.debug { "Setting XMPP avatar for user #{@user.cn}" } + + stanzas = build_xep0084_stanzas + + stanzas.each do |stanza| + payload = { from: @user.address, to: @user.address, stanza: stanza } + res = send_stanza payload + raise res.inspect if res.status != 200 + end + end + end + + private + + def process_avatar + @user.avatar.blob.open do |file| + processed = ImageProcessing::Vips + .source(file) + .resize_to_fill(256, 256) + .convert("png") + .call + processed.read + end + end + + # See https://xmpp.org/extensions/xep-0084.html + def build_xep0084_stanzas + img_data = process_avatar + sha1_hash = Digest::SHA1.hexdigest(img_data) + base64_data = Base64.strict_encode64(img_data) + + [ + """ + + + + + #{base64_data} + + + + + """.strip, + """ + + + + + + + + + + + + """.strip, + ] + end +end diff --git a/app/services/ejabberd_manager/set_default_bookmarks.rb b/app/services/ejabberd_manager/set_default_bookmarks.rb new file mode 100644 index 0000000..4c94e06 --- /dev/null +++ b/app/services/ejabberd_manager/set_default_bookmarks.rb @@ -0,0 +1,31 @@ +module EjabberdManager + class SetDefaultBookmarks < EjabberdManagerService + def initialize(user:) + @user = user + end + + def call + return unless Setting.xmpp_default_rooms.any? + + private_set @user, storage_content + end + + private + + def storage_content + bookmarks = "" + Setting.xmpp_default_rooms.each do |r| + bookmarks << conference_element( + jid: r[/<(.+)>/, 1], name: r[/^(.+)\s/, 1], nick: @user.cn, + autojoin: Setting.xmpp_autojoin_default_rooms + ) + end + + "#{bookmarks}" + end + + def conference_element(jid:, name:, autojoin: false, nick:) + "#{nick}" + end + end +end diff --git a/app/services/ejabberd_api_client.rb b/app/services/ejabberd_manager_service.rb similarity index 76% rename from app/services/ejabberd_api_client.rb rename to app/services/ejabberd_manager_service.rb index df22d25..733e384 100644 --- a/app/services/ejabberd_api_client.rb +++ b/app/services/ejabberd_manager_service.rb @@ -1,11 +1,16 @@ -class EjabberdApiClient - def initialize - @base_url = Setting.ejabberd_api_url +class EjabberdManagerService < RestApiService + private + + def base_url + @base_url ||= Setting.ejabberd_api_url end - def post(endpoint, payload) - Faraday.post "#{@base_url}/#{endpoint}", payload.to_json, - "Content-Type" => "application/json" + def headers + { "Content-Type" => "application/json" } + end + + def parse_responses? + false end # diff --git a/app/services/rest_api_service.rb b/app/services/rest_api_service.rb index e46cfb3..8e23fef 100644 --- a/app/services/rest_api_service.rb +++ b/app/services/rest_api_service.rb @@ -13,15 +13,17 @@ class RestApiService < ApplicationService "#{base_url}/#{path.gsub(/^\//, '')}" end + def parse_responses? + true + end + def get(path, params = {}) res = Faraday.get endpoint_url(path), params, headers - # TODO handle unsuccessful responses with no valid JSON body - JSON.parse(res.body) + parse_responses? ? JSON.parse(res.body) : res end def post(path, payload) res = Faraday.post endpoint_url(path), payload.to_json, headers - # TODO handle unsuccessful responses with no valid JSON body - JSON.parse(res.body) + parse_responses? ? JSON.parse(res.body) : res end end diff --git a/app/services/user_manager/update_avatar.rb b/app/services/user_manager/update_avatar.rb index d03f37b..d3bcc31 100644 --- a/app/services/user_manager/update_avatar.rb +++ b/app/services/user_manager/update_avatar.rb @@ -8,6 +8,7 @@ module UserManager LdapManager::UpdateAvatar.call(user: @user) if Setting.ejabberd_enabled? + return if Rails.env.development? XmppSetAvatarJob.perform_later(user: @user) end end diff --git a/spec/fixtures/files/avatar-base64-png.txt b/spec/fixtures/files/avatar-base64-png.txt new file mode 100644 index 0000000..77cb4bc --- /dev/null +++ b/spec/fixtures/files/avatar-base64-png.txt @@ -0,0 +1 @@ +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 diff --git a/spec/jobs/xmpp_exchange_contacts_job_spec.rb b/spec/jobs/xmpp_exchange_contacts_job_spec.rb index 013a80d..8f25895 100644 --- a/spec/jobs/xmpp_exchange_contacts_job_spec.rb +++ b/spec/jobs/xmpp_exchange_contacts_job_spec.rb @@ -1,5 +1,4 @@ require 'rails_helper' -require 'webmock/rspec' RSpec.describe XmppExchangeContactsJob, type: :job do let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" } @@ -10,19 +9,11 @@ RSpec.describe XmppExchangeContactsJob, type: :job do described_class.perform_later(user, guest) } - before do - stub_request(:post, "http://xmpp.example.com/api/add_rosteritem") - .to_return(status: 200, body: "", headers: {}) - allow_any_instance_of(User).to receive(:services_enabled).and_return(["ejabberd"]) - end + it "calls the service for exchanging contacts" do + expect(EjabberdManager::ExchangeContacts).to receive(:call) + .with(inviter: user, invitee: guest).and_return(true) - it "posts add_rosteritem commands to the ejabberd API" do perform_enqueued_jobs { job } - - expect(WebMock).to have_requested(:post, "http://xmpp.example.com/api/add_rosteritem") - .with { |req| req.body == '{"localuser":"isaacnewton","localhost":"kosmos.org","user":"willherschel","host":"kosmos.org","nick":"willherschel","group":"Buddies","subs":"both"}' } - expect(WebMock).to have_requested(:post, "http://xmpp.example.com/api/add_rosteritem") - .with { |req| req.body == '{"localuser":"willherschel","localhost":"kosmos.org","user":"isaacnewton","host":"kosmos.org","nick":"isaacnewton","group":"Buddies","subs":"both"}' } end after do diff --git a/spec/jobs/xmpp_send_message_spec.rb b/spec/jobs/xmpp_send_message_spec.rb new file mode 100644 index 0000000..60166b9 --- /dev/null +++ b/spec/jobs/xmpp_send_message_spec.rb @@ -0,0 +1,25 @@ +require 'rails_helper' + +RSpec.describe XmppSendMessageJob, type: :job do + let(:payload) {{ + type: "normal", + from: "kosmos.org", to: "willherschel@kosmos.org", + body: "This is a test message" + }} + + subject(:job) { + described_class.perform_later(payload) + } + + it "calls the service for exchanging contacts" do + expect(EjabberdManager::SendMessage).to receive(:call) + .with(payload: payload).and_return(true) + + perform_enqueued_jobs { job } + end + + after do + clear_enqueued_jobs + clear_performed_jobs + end +end diff --git a/spec/jobs/xmpp_set_default_bookmarks_job_spec.rb b/spec/jobs/xmpp_set_default_bookmarks_job_spec.rb index 299cbcc..78c209f 100644 --- a/spec/jobs/xmpp_set_default_bookmarks_job_spec.rb +++ b/spec/jobs/xmpp_set_default_bookmarks_job_spec.rb @@ -1,30 +1,17 @@ require 'rails_helper' -require 'webmock/rspec' RSpec.describe XmppSetDefaultBookmarksJob, type: :job do let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" } - before do - Setting.xmpp_default_rooms = [ - "Welcome ", - "Kosmos Dev " - ] - end - subject(:job) { described_class.perform_later(user) } - before do - stub_request(:post, "http://xmpp.example.com/api/private_set") - .to_return(status: 200, body: "", headers: {}) - end + it "calls the service for setting default bookmarks" do + expect(EjabberdManager::SetDefaultBookmarks).to receive(:call) + .with(user: user).and_return(true) - it "posts a private_set command to the ejabberd API" do perform_enqueued_jobs { job } - - expect(WebMock).to have_requested(:post, "http://xmpp.example.com/api/private_set") - .with { |req| req.body == '{"user":"willherschel","host":"kosmos.org","element":"\u003cstorage xmlns=\'storage:bookmarks\'\u003e\u003cconference jid=\'welcome@kosmos.chat\' name=\'Welcome\' autojoin=\'false\'\u003e\u003cnick\u003ewillherschel\u003c/nick\u003e\u003c/conference\u003e\u003cconference jid=\'kosmos-dev@kosmos.chat\' name=\'Kosmos Dev\' autojoin=\'false\'\u003e\u003cnick\u003ewillherschel\u003c/nick\u003e\u003c/conference\u003e\u003c/storage\u003e"}' } end after do diff --git a/spec/services/ejabberd_manager/exchange_contacts_spec.rb b/spec/services/ejabberd_manager/exchange_contacts_spec.rb new file mode 100644 index 0000000..b15e436 --- /dev/null +++ b/spec/services/ejabberd_manager/exchange_contacts_spec.rb @@ -0,0 +1,22 @@ +require 'rails_helper' +require 'webmock/rspec' + +RSpec.describe EjabberdManager::ExchangeContacts, type: :model do + let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" } + let(:guest) { create :user, cn: "isaacnewton", ou: "kosmos.org", + id: 2, email: "hotapple42@eol.com" } + + before do + stub_request(:post, "http://xmpp.example.com/api/add_rosteritem") + .to_return(status: 200, body: "", headers: {}) + allow_any_instance_of(User).to receive(:services_enabled).and_return(["ejabberd"]) + described_class.call(inviter: user, invitee: guest) + end + + it "posts add_rosteritem commands to the ejabberd API" do + expect(WebMock).to have_requested(:post, "http://xmpp.example.com/api/add_rosteritem") + .with { |req| req.body == '{"localuser":"isaacnewton","localhost":"kosmos.org","user":"willherschel","host":"kosmos.org","nick":"willherschel","group":"Buddies","subs":"both"}' } + expect(WebMock).to have_requested(:post, "http://xmpp.example.com/api/add_rosteritem") + .with { |req| req.body == '{"localuser":"willherschel","localhost":"kosmos.org","user":"isaacnewton","host":"kosmos.org","nick":"isaacnewton","group":"Buddies","subs":"both"}' } + end +end diff --git a/spec/services/ejabberd_manager/get_avatar_spec.rb b/spec/services/ejabberd_manager/get_avatar_spec.rb new file mode 100644 index 0000000..f7772d5 --- /dev/null +++ b/spec/services/ejabberd_manager/get_avatar_spec.rb @@ -0,0 +1,37 @@ +require 'rails_helper' +require 'webmock/rspec' + +RSpec.describe EjabberdManager::GetAvatar, type: :model do + let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" } + let(:img_base64) { File.read("#{Rails.root}/spec/fixtures/files/avatar-base64-png.txt").chomp } + + context "when no avatar is set yet" do + before do + stub_request(:post, "http://xmpp.example.com/api/get_vcard2") + .with { |req| req.body == '{"user":"willherschel","host":"kosmos.org","name":"PHOTO","subname":"BINVAL"}' } + .to_return(status: 400, body: "", headers: {}) + end + + it "returns nil" do + res = described_class.call(user: user) + expect(res).to be_nil + end + end + + context "when avatar exists" do + before do + stub_request(:post, "http://xmpp.example.com/api/get_vcard2") + .with { |req| req.body == '{"user":"willherschel","host":"kosmos.org","name":"PHOTO","subname":"BINVAL"}' } + .and_return(status: 200, body: { content: img_base64 }.to_json, headers: {}) + stub_request(:post, "http://xmpp.example.com/api/get_vcard2") + .with { |req| req.body == '{"user":"willherschel","host":"kosmos.org","name":"PHOTO","subname":"TYPE"}' } + .and_return(status: 200, body: { content: "image/png" }.to_json, headers: {}) + end + + it "fetches the avatar and content type" do + res = described_class.call(user: user) + expect(res[:img_base64]).to eq(img_base64) + expect(res[:content_type]).to eq("image/png") + end + end +end diff --git a/spec/services/ejabberd_manager/set_default_bookmarks_spec.rb b/spec/services/ejabberd_manager/set_default_bookmarks_spec.rb new file mode 100644 index 0000000..2986a4c --- /dev/null +++ b/spec/services/ejabberd_manager/set_default_bookmarks_spec.rb @@ -0,0 +1,22 @@ +require 'rails_helper' +require 'webmock/rspec' + +RSpec.describe EjabberdManager::SetDefaultBookmarks, type: :model do + let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" } + + before do + Setting.xmpp_default_rooms = [ + "Welcome ", + "Kosmos Dev " + ] + stub_request(:post, "http://xmpp.example.com/api/private_set") + .to_return(status: 200, body: "", headers: {}) + + described_class.call(user:) + end + + it "posts a private_set command to the ejabberd API" do + expect(WebMock).to have_requested(:post, "http://xmpp.example.com/api/private_set") + .with { |req| req.body == '{"user":"willherschel","host":"kosmos.org","element":"\u003cstorage xmlns=\'storage:bookmarks\'\u003e\u003cconference jid=\'welcome@kosmos.chat\' name=\'Welcome\' autojoin=\'false\'\u003e\u003cnick\u003ewillherschel\u003c/nick\u003e\u003c/conference\u003e\u003cconference jid=\'kosmos-dev@kosmos.chat\' name=\'Kosmos Dev\' autojoin=\'false\'\u003e\u003cnick\u003ewillherschel\u003c/nick\u003e\u003c/conference\u003e\u003c/storage\u003e"}' } + end +end