From 819ecf6ad8e3d8633c5507d8050ae7dc4d8c0693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 4 Apr 2024 13:27:52 +0300 Subject: [PATCH 1/3] Add `#service_enabled?` method to user model --- app/controllers/services/chat_controller.rb | 2 +- .../services/mastodon_controller.rb | 2 +- .../services/remotestorage_controller.rb | 2 +- app/jobs/xmpp_exchange_contacts_job.rb | 4 ++-- app/models/user.rb | 4 ++++ spec/models/user_spec.rb | 19 +++++++++++++++++++ 6 files changed, 28 insertions(+), 5 deletions(-) diff --git a/app/controllers/services/chat_controller.rb b/app/controllers/services/chat_controller.rb index dfc69e3..dda2be5 100644 --- a/app/controllers/services/chat_controller.rb +++ b/app/controllers/services/chat_controller.rb @@ -3,7 +3,7 @@ class Services::ChatController < Services::BaseController before_action :require_service_available def show - @service_enabled = current_user.services_enabled.include?(:xmpp) + @service_enabled = current_user.service_enabled?(:xmpp) end private diff --git a/app/controllers/services/mastodon_controller.rb b/app/controllers/services/mastodon_controller.rb index ddea323..7958b8b 100644 --- a/app/controllers/services/mastodon_controller.rb +++ b/app/controllers/services/mastodon_controller.rb @@ -3,7 +3,7 @@ class Services::MastodonController < Services::BaseController before_action :require_service_available def show - @service_enabled = current_user.services_enabled.include?(:mastodon) + @service_enabled = current_user.service_enabled?(:mastodon) end private diff --git a/app/controllers/services/remotestorage_controller.rb b/app/controllers/services/remotestorage_controller.rb index 632e5c7..15726d4 100644 --- a/app/controllers/services/remotestorage_controller.rb +++ b/app/controllers/services/remotestorage_controller.rb @@ -5,7 +5,7 @@ class Services::RemotestorageController < Services::BaseController # Dashboard def show - # unless current_user.services_enabled.include?(:remotestorage) + # unless current_user.service_enabled?(:remotestorage) # redirect_to service_remotestorage_info_path # end @rs_auths = current_user.remote_storage_authorizations diff --git a/app/jobs/xmpp_exchange_contacts_job.rb b/app/jobs/xmpp_exchange_contacts_job.rb index f1b20e0..1a9d67a 100644 --- a/app/jobs/xmpp_exchange_contacts_job.rb +++ b/app/jobs/xmpp_exchange_contacts_job.rb @@ -2,8 +2,8 @@ class XmppExchangeContactsJob < ApplicationJob queue_as :default def perform(inviter, invitee) - return unless inviter.services_enabled.include?("xmpp") && - invitee.services_enabled.include?("xmpp") && + return unless inviter.service_enabled?(:xmpp) && + invitee.service_enabled?(:xmpp) && inviter.preferences[:xmpp_exchange_contacts_with_invitees] ejabberd = EjabberdApiClient.new diff --git a/app/models/user.rb b/app/models/user.rb index f0609a6..735d7e0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -180,6 +180,10 @@ class User < ApplicationRecord ldap_entry[:services_enabled] || [] end + def service_enabled?(name) + services_enabled.map(&:to_sym).include?(name.to_sym) + end + def enable_service(service) current_services = services_enabled new_services = Array(service).map(&:to_s) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index b0d077e..1646d1c 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -80,6 +80,25 @@ RSpec.describe User, type: :model do end end + describe "#service_enabled?" do + before do + allow(user).to receive(:ldap_entry).and_return({ + uid: user.cn, ou: user.ou, mail: user.email, admin: nil, + services_enabled: ["gitea", "xmpp"] + }) + end + + it "returns true or false" do + expect(user.service_enabled?("gitea")).to be(true) + expect(user.service_enabled?("email")).to be(false) + end + + it "returns false when service is not enabled" do + expect(user.service_enabled?(:gitea)).to be(true) + expect(user.service_enabled?(:email)).to be(false) + end + end + describe "#enable_service" do before do allow(user).to receive(:ldap_entry).and_return({ -- 2.25.1 From 8f600f44bda67e37392cea1a9c8098162e180e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 4 Apr 2024 17:17:17 +0300 Subject: [PATCH 2/3] Add Mastodon aliases and links to Webfinger when enabled Also requires "remotestorage" service to be enabled via attribute --- .env.test | 2 + app/controllers/webfinger_controller.rb | 59 ++++++++++--- spec/requests/webfinger_spec.rb | 110 +++++++++++++++++++++--- 3 files changed, 149 insertions(+), 22 deletions(-) diff --git a/.env.test b/.env.test index aadec95..92e93c8 100644 --- a/.env.test +++ b/.env.test @@ -11,6 +11,8 @@ DISCOURSE_CONNECT_SECRET='discourse_connect_ftw' EJABBERD_API_URL='http://xmpp.example.com/api' +MASTODON_PUBLIC_URL='http://example.social' + LNDHUB_API_URL='http://localhost:3026' LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' LNDHUB_PUBLIC_KEY='024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946' diff --git a/app/controllers/webfinger_controller.rb b/app/controllers/webfinger_controller.rb index 02c40f9..2369313 100644 --- a/app/controllers/webfinger_controller.rb +++ b/app/controllers/webfinger_controller.rb @@ -7,14 +7,15 @@ class WebfingerController < ApplicationController resource = params[:resource] if resource && @useraddress = resource.match(/acct:(.+)/)&.[](1) - @username, @org = @useraddress.split("@") + @username, @domain = @useraddress.split("@") unless Rails.env.development? # Allow different domains (e.g. localhost:3000) in development only - head 404 and return unless @org == Setting.primary_domain + head 404 and return unless @domain == Setting.primary_domain end - unless User.where(cn: @username.downcase, ou: Setting.primary_domain).any? + unless @user = User.where(ou: Setting.primary_domain) + .find_by(cn: @username.downcase) head 404 and return end @@ -28,12 +29,50 @@ class WebfingerController < ApplicationController private def webfinger - links = []; + jrd = { + subject: "acct:#{@user.address}", + aliases: [], + links: [] + } - # TODO check if storage service is enabled for user, not just globally - links << remotestorage_link if Setting.remotestorage_enabled + if Setting.mastodon_enabled && @user.service_enabled?(:mastodon) + # https://docs.joinmastodon.org/spec/webfinger/ + jrd[:aliases] += mastodon_aliases + jrd[:links] += mastodon_links + end - { "links" => links } + if Setting.remotestorage_enabled && @user.service_enabled?(:remotestorage) + # https://datatracker.ietf.org/doc/draft-dejong-remotestorage/ + jrd[:links] << remotestorage_link + end + + jrd + end + + def mastodon_aliases + [ + "#{Setting.mastodon_public_url}/@#{@user.cn}", + "#{Setting.mastodon_public_url}/users/#{@user.cn}" + ] + end + + def mastodon_links + [ + { + rel: "http://webfinger.net/rel/profile-page", + type: "text/html", + href: "#{Setting.mastodon_public_url}/@#{@user.cn}" + }, + { + rel: "self", + type: "application/activity+json", + href: "#{Setting.mastodon_public_url}/users/#{@user.cn}" + }, + { + rel: "http://ostatus.org/schema/1.0/subscribe", + template: "#{Setting.mastodon_public_url}/authorize_interaction?uri={uri}" + } + ] end def remotestorage_link @@ -41,9 +80,9 @@ class WebfingerController < ApplicationController storage_url = "#{Setting.rs_storage_url}/#{@username}" { - "rel" => "http://tools.ietf.org/id/draft-dejong-remotestorage", - "href" => storage_url, - "properties" => { + rel: "http://tools.ietf.org/id/draft-dejong-remotestorage", + href: storage_url, + properties: { "http://remotestorage.io/spec/version" => "draft-dejong-remotestorage-13", "http://tools.ietf.org/html/rfc6749#section-4.2" => auth_url, "http://tools.ietf.org/html/rfc6750#section-2.3" => nil, # access token via a HTTP query parameter diff --git a/spec/requests/webfinger_spec.rb b/spec/requests/webfinger_spec.rb index 27cd361..8bb0d93 100644 --- a/spec/requests/webfinger_spec.rb +++ b/spec/requests/webfinger_spec.rb @@ -1,13 +1,87 @@ require 'rails_helper' RSpec.describe "WebFinger", type: :request do - describe "remoteStorage link relation" do - context "user exists" do - before do - create :user, cn: 'tony', ou: 'kosmos.org' + describe "User does not exist" do + it "returns a 404 status" do + get "/.well-known/webfinger?resource=acct%3Ajane.doe%40kosmos.org" + expect(response).to have_http_status(:not_found) + end + end + + context "User exists" do + let(:user) { create :user, cn: 'tony', ou: 'kosmos.org' } + + before do + allow_any_instance_of(User).to receive(:ldap_entry).and_return({ + uid: user.cn, ou: user.ou, mail: user.email, admin: nil, + services_enabled: ["mastodon", "remotestorage"] + }) + end + + describe "Mastodon entries" do + context "Mastodon available" do + it "includes the Mastodon aliases and links for the user" do + get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org" + expect(response).to have_http_status(:ok) + + res = JSON.parse(response.body) + + expect(res["aliases"]).to include("http://example.social/@tony") + expect(res["aliases"]).to include("http://example.social/users/tony") + + profile_link = res["links"].find{|l| l["rel"] == "http://webfinger.net/rel/profile-page"} + self_link = res["links"].find{|l| l["rel"] == "self"} + ostatus_link = res["links"].find{|l| l["rel"] == "http://ostatus.org/schema/1.0/subscribe"} + expect(profile_link["type"]).to eql("text/html") + expect(profile_link["href"]).to eql("http://example.social/@tony") + expect(self_link["type"]).to eql("application/activity+json") + expect(self_link["href"]).to eql("http://example.social/users/tony") + expect(ostatus_link["template"]).to eql("http://example.social/authorize_interaction?uri={uri}") + end end - context "remoteStorage enabled globally" do + context "Mastodon not enabled for user" do + before do + allow_any_instance_of(User).to receive(:ldap_entry).and_return({ + uid: user.cn, ou: user.ou, mail: user.email, admin: nil, + services_enabled: ["xmpp"] + }) + end + + it "does not include Mastodon aliases or links" do + get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org" + expect(response).to have_http_status(:ok) + + res = JSON.parse(response.body) + expect(res["aliases"]).not_to include("http://example.social/@tony") + expect(res["aliases"]).not_to include("http://example.social/users/tony") + expect(res["links"].find{|l| l["rel"] == "http://webfinger.net/rel/profile-page"}).to be(nil) + expect(res["links"].find{|l| l["rel"] == "self"}).to be(nil) + expect(res["links"].find{|l| l["rel"] == "http://ostatus.org/schema/1.0/subscribe"}).to be(nil) + end + end + + context "Mastodon not available" do + before do + Setting.mastodon_enabled = false + end + + it "does not include Mastodon aliases or links" do + get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org" + expect(response).to have_http_status(:ok) + + res = JSON.parse(response.body) + expect(res["aliases"]).not_to include("http://example.social/@tony") + expect(res["aliases"]).not_to include("http://example.social/users/tony") + expect(res["links"].find{|l| l["rel"] == "http://webfinger.net/rel/profile-page"}).to be(nil) + expect(res["links"].find{|l| l["rel"] == "self"}).to be(nil) + expect(res["links"].find{|l| l["rel"] == "http://ostatus.org/schema/1.0/subscribe"}).to be(nil) + end + end + end + + describe "remoteStorage entries" do + context "remoteStorage available" do it "includes the remoteStorage link for the user" do get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org" expect(response).to have_http_status(:ok) @@ -22,6 +96,25 @@ RSpec.describe "WebFinger", type: :request do end end + context "remoteStorage not enabled for user" do + before do + allow_any_instance_of(User).to receive(:ldap_entry).and_return({ + uid: user.cn, ou: user.ou, mail: user.email, admin: nil, + services_enabled: ["xmpp"] + }) + end + + it "does not include the remoteStorage link" do + get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org" + expect(response).to have_http_status(:ok) + + res = JSON.parse(response.body) + rs_link = res["links"].find {|l| l["rel"] == "http://tools.ietf.org/id/draft-dejong-remotestorage"} + + expect(rs_link).to be_nil + end + end + context "remoteStorage not available" do before do Setting.remotestorage_enabled = false @@ -38,12 +131,5 @@ RSpec.describe "WebFinger", type: :request do end end end - - context "user does not exist" do - it "does return a 404 status" do - get "/.well-known/webfinger?resource=acct%3Ajane.doe%40kosmos.org" - expect(response).to have_http_status(:not_found) - end - end end end -- 2.25.1 From 78aff3d796e6a2dc2ce50a628581ea067081adb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 4 Apr 2024 17:22:57 +0300 Subject: [PATCH 3/3] Fix spec The test env has Mastodon enabled now --- spec/models/user_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 1646d1c..04af369 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -16,6 +16,10 @@ RSpec.describe User, type: :model do let(:user) { build :user, cn: "jimmy", ou: "kosmos.org" } context "Mastodon service not configured" do + before do + Setting.mastodon_enabled = false + end + it "returns nil" do expect(user.mastodon_address).to be_nil end -- 2.25.1