Refactor ejabberd API integration #226

Merged
greg merged 1 commits from core/refactor_ejabberd_integration into master 2025-05-28 09:22:39 +00:00
19 changed files with 307 additions and 167 deletions

No files matched your search

+1 -16
View File
@@ -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
+1 -2
View File
@@ -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
+1 -91
View File
@@ -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)
[
"""
<iq type='set' from='#{@user.address}' id='avatar-data-#{rand(101)}'>
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
<publish node='urn:xmpp:avatar:data'>
<item id='#{sha1_hash}'>
<data xmlns='urn:xmpp:avatar:data'>#{base64_data}</data>
</item>
</publish>
</pubsub>
</iq>
""".strip,
"""
<iq type='set' from='#{@user.address}' id='avatar-metadata-#{rand(101)}'>
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
<publish node='urn:xmpp:avatar:metadata'>
<item id='#{sha1_hash}'>
<metadata xmlns='urn:xmpp:avatar:metadata'>
<info bytes='#{img_data.size}'
id='#{sha1_hash}'
height='256'
type='image/png'
width='256'/>
</metadata>
</item>
</publish>
</pubsub>
</iq>
""".strip,
]
end
end
+1 -20
View File
@@ -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
"<storage xmlns='storage:bookmarks'>#{bookmarks}</storage>"
end
def conference_element(jid:, name:, autojoin: false, nick:)
"<conference jid='#{jid}' name='#{name}' autojoin='#{autojoin.to_s}'><nick>#{nick}</nick></conference>"
EjabberdManager::SetDefaultBookmarks.call(user:)
end
end
@@ -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
@@ -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
@@ -0,0 +1,11 @@
module EjabberdManager
class SendMessage < EjabberdManagerService
def initialize(payload:)
@payload = payload
end
def call
send_message @payload
end
end
end
@@ -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)
[
"""
<iq type='set' from='#{@user.address}' id='avatar-data-#{rand(101)}'>
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
<publish node='urn:xmpp:avatar:data'>
<item id='#{sha1_hash}'>
<data xmlns='urn:xmpp:avatar:data'>#{base64_data}</data>
</item>
</publish>
</pubsub>
</iq>
""".strip,
"""
<iq type='set' from='#{@user.address}' id='avatar-metadata-#{rand(101)}'>
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
<publish node='urn:xmpp:avatar:metadata'>
<item id='#{sha1_hash}'>
<metadata xmlns='urn:xmpp:avatar:metadata'>
<info bytes='#{img_data.size}'
id='#{sha1_hash}'
height='256'
type='image/png'
width='256'/>
</metadata>
</item>
</publish>
</pubsub>
</iq>
""".strip,
]
end
end
@@ -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
"<storage xmlns='storage:bookmarks'>#{bookmarks}</storage>"
end
def conference_element(jid:, name:, autojoin: false, nick:)
"<conference jid='#{jid}' name='#{name}' autojoin='#{autojoin.to_s}'><nick>#{nick}</nick></conference>"
end
end
end
@@ -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
#
+6 -4
View File
@@ -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
@@ -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
+1
View File
@@ -0,0 +1 @@
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 truncated
+3 -12
View File
@@ -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
+25
View File
@@ -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
@@ -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 <welcome@kosmos.chat>",
"Kosmos Dev <kosmos-dev@kosmos.chat>"
]
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
@@ -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
@@ -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
@@ -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 <welcome@kosmos.chat>",
"Kosmos Dev <kosmos-dev@kosmos.chat>"
]
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