diff --git a/app/controllers/diaspora_federation/webfinger_controller.rb b/app/controllers/diaspora_federation/webfinger_controller.rb index da275de..5aa9a99 100644 --- a/app/controllers/diaspora_federation/webfinger_controller.rb +++ b/app/controllers/diaspora_federation/webfinger_controller.rb @@ -5,20 +5,6 @@ require_dependency "diaspora_federation/application_controller" module DiasporaFederation # This controller handles all webfinger-specific requests. class WebfingerController < ApplicationController - # Returns the host-meta xml - # - # example: - # - # - # - # - # - # GET /.well-known/host-meta - def host_meta - render xml: WebfingerController.host_meta_xml, content_type: "application/xrd+xml" - end - # Returns the webfinger as RFC 7033 JRD or XRD. # # JSON example: @@ -59,20 +45,6 @@ module DiasporaFederation # ] # } # - # XML example: - # - # - # acct:alice@localhost:3000 - # http://localhost:3000/people/c8e87290f6a20132963908fbffceb188 - # - # - # - # - # - # - # # GET /.well-known/webfinger?resource= def webfinger person_wf = find_person_webfinger(params.require(:resource)) @@ -80,26 +52,13 @@ module DiasporaFederation if person_wf.nil? head :not_found else - logger.info "webfinger profile request for: #{person_wf.acct_uri}" + logger.info "Webfinger profile request for: #{person_wf.acct_uri}" - respond_to do |format| - format.any(:jrd, :json, :html) do - headers["Access-Control-Allow-Origin"] = "*" - render json: JSON.pretty_generate(person_wf.to_json), content_type: "application/jrd+json" - end - format.any(:xrd, :xml) do - render xml: person_wf.to_xml, content_type: "application/xrd+xml" - end - end + headers["Access-Control-Allow-Origin"] = "*" + render json: JSON.pretty_generate(person_wf.to_json), content_type: "application/jrd+json" end end - # Creates the host-meta xml with the configured server_uri and caches it - # @return [String] XML string - def self.host_meta_xml - @host_meta_xml ||= Discovery::HostMeta.from_base_url(DiasporaFederation.server_uri.to_s).to_xml - end - private def find_person_webfinger(query) diff --git a/config/routes.rb b/config/routes.rb index 2c34046..0fd5e93 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,7 +11,6 @@ DiasporaFederation::Engine.routes.draw do end controller :webfinger do - get ".well-known/host-meta" => :host_meta, :as => "host_meta" get ".well-known/webfinger" => :webfinger, :as => "webfinger" end diff --git a/lib/diaspora_federation/discovery.rb b/lib/diaspora_federation/discovery.rb index ab587c9..6b80b8a 100644 --- a/lib/diaspora_federation/discovery.rb +++ b/lib/diaspora_federation/discovery.rb @@ -10,7 +10,6 @@ end require "diaspora_federation/discovery/exceptions" require "diaspora_federation/discovery/xrd_document" -require "diaspora_federation/discovery/host_meta" require "diaspora_federation/discovery/web_finger" require "diaspora_federation/discovery/h_card" require "diaspora_federation/discovery/discovery" diff --git a/lib/diaspora_federation/discovery/discovery.rb b/lib/diaspora_federation/discovery/discovery.rb index ac637af..3646ced 100644 --- a/lib/diaspora_federation/discovery/discovery.rb +++ b/lib/diaspora_federation/discovery/discovery.rb @@ -24,7 +24,7 @@ module DiasporaFederation validate_diaspora_id DiasporaFederation.callbacks.trigger(:save_person_after_webfinger, person) - logger.info "successfully webfingered #{diaspora_id}" + logger.info "Successfully webfingered #{diaspora_id}" person rescue DiscoveryError raise # simply re-raise DiscoveryError @@ -67,22 +67,11 @@ module DiasporaFederation "acct:#{diaspora_id}" end - def legacy_webfinger_url_from_host_meta - # This tries the xrd url with https first, then falls back to http. - host_meta = HostMeta.from_xml(get("https://#{domain}/.well-known/host-meta", http_fallback: true)) - host_meta.webfinger_template_url.gsub("{uri}", acct_parameter) - end - def webfinger - return @webfinger if @webfinger - - webfinger_url = "https://#{domain}/.well-known/webfinger?resource=#{acct_parameter}" - # This tries the WebFinger URL with https first, then falls back to http if webfinger_http_fallback is enabled. - @webfinger = WebFinger.from_json(get(webfinger_url, http_fallback: DiasporaFederation.webfinger_http_fallback)) - rescue => e # rubocop:disable Style/RescueStandardError - logger.warn "WebFinger failed, retrying with legacy WebFinger for #{diaspora_id}: #{e.class}: #{e.message}" - @webfinger = WebFinger.from_xml(get(legacy_webfinger_url_from_host_meta)) + @webfinger ||= + WebFinger.from_json(get("https://#{domain}/.well-known/webfinger?resource=#{acct_parameter}", + http_fallback: DiasporaFederation.webfinger_http_fallback)) end def hcard diff --git a/lib/diaspora_federation/discovery/host_meta.rb b/lib/diaspora_federation/discovery/host_meta.rb deleted file mode 100644 index 80de836..0000000 --- a/lib/diaspora_federation/discovery/host_meta.rb +++ /dev/null @@ -1,92 +0,0 @@ -# frozen_string_literal: true - -module DiasporaFederation - module Discovery - # Generates and parses Host Meta documents. - # - # This is a minimal implementation of the standard, only to the degree of what - # is used for the purposes of the diaspora* protocol. (e.g. WebFinger) - # - # @example Creating a Host Meta document - # doc = HostMeta.from_base_url("https://pod.example.tld/") - # doc.to_xml - # - # @example Parsing a Host Meta document - # doc = HostMeta.from_xml(xml_string) - # webfinger_tpl = doc.webfinger_template_url - # - # @see http://tools.ietf.org/html/rfc6415 RFC 6415: "Web Host Metadata" - # @see XrdDocument - class HostMeta - private_class_method :new - - # Creates a new host-meta instance - # @param [String] webfinger_url the webfinger-url - def initialize(webfinger_url) - @webfinger_url = webfinger_url - end - - # URL fragment to append to the base URL - WEBFINGER_SUFFIX = "/.well-known/webfinger.xml?resource={uri}" - - # Returns the WebFinger URL that was used to build this instance (either from - # xml or by giving a base URL). - # @return [String] WebFinger template URL - def webfinger_template_url - @webfinger_url - end - - # Produces the XML string for the Host Meta instance with a +Link+ element - # containing the +webfinger_url+. - # @return [String] XML string - def to_xml - doc = XrdDocument.new - doc.links << {rel: "lrdd", - type: "application/xrd+xml", - template: @webfinger_url} - doc.to_xml - end - - # Builds a new HostMeta instance and constructs the WebFinger URL from the - # given base URL by appending HostMeta::WEBFINGER_SUFFIX. - # @param [String, URL] base_url the base-url for the webfinger-url - # @return [HostMeta] - # @raise [InvalidData] if the webfinger url is malformed - def self.from_base_url(base_url) - webfinger_url = "#{base_url.to_s.chomp('/')}#{WEBFINGER_SUFFIX}" - raise InvalidData, "invalid webfinger url: #{webfinger_url}" unless webfinger_url_valid?(webfinger_url) - - new(webfinger_url) - end - - # Reads the given Host Meta XML document string and populates the - # +webfinger_url+. - # @param [String] hostmeta_xml Host Meta XML string - # @raise [InvalidData] if the xml or the webfinger url is malformed - def self.from_xml(hostmeta_xml) - data = XrdDocument.xml_data(hostmeta_xml) - raise InvalidData, "received an invalid xml" unless data.key?(:links) - - webfinger_url = webfinger_url_from_xrd(data) - raise InvalidData, "invalid webfinger url: #{webfinger_url}" unless webfinger_url_valid?(webfinger_url) - - new(webfinger_url) - end - - # Applies some basic sanity-checking to the given URL - # @param [String] url validation subject - # @return [Boolean] validation result - private_class_method def self.webfinger_url_valid?(url) - !url.nil? && url.instance_of?(String) && url =~ %r{\Ahttps?://.*/.*\{uri\}.*}i - end - - # Gets the webfinger url from an XRD data structure - # @param [Hash] data extracted data - # @return [String] webfinger url - private_class_method def self.webfinger_url_from_xrd(data) - link = data[:links].find {|l| l[:rel] == "lrdd" } - return link[:template] unless link.nil? - end - end - end -end diff --git a/lib/diaspora_federation/discovery/web_finger.rb b/lib/diaspora_federation/discovery/web_finger.rb index b56fb9c..cc3bc9f 100644 --- a/lib/diaspora_federation/discovery/web_finger.rb +++ b/lib/diaspora_federation/discovery/web_finger.rb @@ -3,10 +3,7 @@ module DiasporaFederation module Discovery # The WebFinger document used for diaspora* user discovery is based on an - # {http://tools.ietf.org/html/draft-jones-appsawg-webfinger older draft of the specification}. - # - # In the meantime an actual RFC draft has been in development, which should - # serve as a base for all future changes of this implementation. + # {https://datatracker.ietf.org/doc/html/rfc7033 RFC 7033}. # # @example Creating a WebFinger document from a person hash # wf = WebFinger.new( @@ -17,22 +14,20 @@ module DiasporaFederation # atom_url: "https://server.example/public/user.atom", # salmon_url: "https://server.example/receive/users/0123456789abcdef" # ) - # xml_string = wf.to_xml + # json_string = wf.to_json # - # @example Creating a WebFinger instance from an xml document - # wf = WebFinger.from_xml(xml_string) + # @example Creating a WebFinger instance from an JSON document + # wf = WebFinger.from_json(json_string) # ... # hcard_url = wf.hcard_url # ... # - # @see http://tools.ietf.org/html/draft-jones-appsawg-webfinger "WebFinger" - - # current draft # @see http://www.iana.org/assignments/link-relations/link-relations.xhtml # official list of IANA link relations class WebFinger < Entity # @!attribute [r] acct_uri - # The Subject element should contain the webfinger address that was asked - # for. If it does not, then this webfinger profile MUST be ignored. + # The Subject element should contain the WebFinger address that was asked + # for. If it does not, then this WebFinger profile MUST be ignored. # @return [String] property :acct_uri, :string @@ -67,7 +62,7 @@ module DiasporaFederation property :salmon_url, :string, optional: true # @!attribute [r] subscribe_url - # This url is used to find another user on the home-pod of the user in the webfinger. + # This url is used to find another user on the home-pod of the user in the WebFinger. property :subscribe_url, :string, optional: true # +hcard_url+ link relation @@ -106,29 +101,29 @@ module DiasporaFederation super(data) end - # Creates the XML string from the current WebFinger instance - # @return [String] XML string + # @!visibility private + # Generating WebFinger to XML is not supported anymore, use {#to_json} instead. def to_xml - to_xrd.to_xml + raise "Generating WebFinger to XML is not supported anymore, use 'to_json' instead." end + # Creates the JSON string from the current WebFinger instance + # @return [String] JSON string def to_json(*_args) to_xrd.to_json end - # Creates a WebFinger instance from the given XML string - # @param [String] webfinger_xml WebFinger XML string - # @return [WebFinger] WebFinger instance - # @raise [InvalidData] if the given XML string is invalid or incomplete - def self.from_xml(webfinger_xml) - from_hash(parse_xml_and_validate(webfinger_xml)) + # @!visibility private + # Parsing WebFinger as XML is not supported anymore, use {from_json} instead. + def self.from_xml(_webfinger_xml) + raise "Parsing WebFinger as XML is not supported anymore, use 'from_json' instead." end # Creates a WebFinger instance from the given JSON string # @param [String] webfinger_json WebFinger JSON string # @return [WebFinger] WebFinger instance def self.from_json(webfinger_json) - from_hash(XrdDocument.json_data(webfinger_json)) + from_hash(parse_json_and_validate(webfinger_json)) end # Creates a WebFinger instance from the given data @@ -157,15 +152,15 @@ module DiasporaFederation private - # Parses the XML string to a Hash and does some rudimentary checking on + # Parses the JSON string to a Hash and does some rudimentary checking on # the data Hash. - # @param [String] webfinger_xml WebFinger XML string - # @return [Hash] data XML data - # @raise [InvalidData] if the given XML string is invalid or incomplete - private_class_method def self.parse_xml_and_validate(webfinger_xml) - XrdDocument.xml_data(webfinger_xml).tap do |data| + # @param [String] webfinger_json WebFinger JSON string + # @return [Hash] data JSON data + # @raise [InvalidData] if the given JSON string is invalid or incomplete + private_class_method def self.parse_json_and_validate(webfinger_json) + XrdDocument.json_data(webfinger_json).tap do |data| valid = data.key?(:subject) && data.key?(:links) - raise InvalidData, "webfinger xml is incomplete" unless valid + raise InvalidData, "Webfinger JSON is incomplete" unless valid end end diff --git a/lib/diaspora_federation/discovery/xrd_document.rb b/lib/diaspora_federation/discovery/xrd_document.rb index eecab2e..576bfff 100644 --- a/lib/diaspora_federation/discovery/xrd_document.rb +++ b/lib/diaspora_federation/discovery/xrd_document.rb @@ -5,13 +5,9 @@ module DiasporaFederation # This class implements basic handling of XRD documents as far as it is # necessary in the context of the protocols used with diaspora* federation. # - # @note {http://tools.ietf.org/html/rfc6415 RFC 6415} recommends that servers - # should also offer the JRD format in addition to the XRD representation. - # Implementing +XrdDocument#to_json+ and +XrdDocument.json_data+ should - # be almost trivial due to the simplicity of the format and the way the data - # is stored internally already. See - # {http://tools.ietf.org/html/rfc6415#appendix-A RFC 6415, Appendix A} - # for a description of the JSON format. + # It also implements handling of the JRD format, see + # {https://datatracker.ietf.org/doc/html/rfc6415#appendix-A RFC 6415, Appendix A} + # for a description of the JSON format. # # @example Creating a XrdDocument # doc = XrdDocument.new @@ -210,7 +206,7 @@ module DiasporaFederation # symbolize link keys from JSON hash, but only convert known keys private_class_method def self.symbolize_keys_for_links(links) - links.map do |link| + links&.map do |link| {}.tap do |hash| LINK_ATTRS.each do |attr| hash[attr] = link[attr.to_s] if link.key?(attr.to_s) diff --git a/spec/controllers/diaspora_federation/webfinger_controller_spec.rb b/spec/controllers/diaspora_federation/webfinger_controller_spec.rb index 1e0af9d..f8d82bb 100644 --- a/spec/controllers/diaspora_federation/webfinger_controller_spec.rb +++ b/spec/controllers/diaspora_federation/webfinger_controller_spec.rb @@ -4,39 +4,6 @@ module DiasporaFederation describe WebfingerController, type: :controller do routes { DiasporaFederation::Engine.routes } - describe "GET #host_meta" do - before do - DiasporaFederation.server_uri = URI("http://localhost:3000/") - WebfingerController.instance_variable_set(:@host_meta_xml, nil) # clear cache - end - - it "succeeds" do - get :host_meta - expect(response).to be_successful - end - - it "contains the webfinger-template" do - get :host_meta - expect(response.body).to include "template=\"http://localhost:3000/.well-known/webfinger.xml?resource={uri}\"" - end - - it "returns a application/xrd+xml" do - get :host_meta - expect(response.header["Content-Type"]).to include "application/xrd+xml" - end - - it "calls Discovery::HostMeta.from_base_url with the base url" do - expect(Discovery::HostMeta).to receive(:from_base_url).with("http://localhost:3000/").and_call_original - get :host_meta - end - - it "caches the xml" do - expect(Discovery::HostMeta).to receive(:from_base_url).exactly(1).times.and_call_original - get :host_meta - get :host_meta - end - end - describe "GET #webfinger" do it "uses the JRD format as default" do get :webfinger, params: {resource: alice.diaspora_id} @@ -44,81 +11,46 @@ module DiasporaFederation expect(response.header["Content-Type"]).to include "application/jrd+json" end - context "json", exceptions: :catch do - it "succeeds when the person exists" do - get :webfinger, format: :json, params: {resource: alice.diaspora_id} - expect(response).to be_successful - end - - it "succeeds with 'acct:' in the query when the person exists" do - get :webfinger, format: :json, params: {resource: "acct:#{alice.diaspora_id}"} - expect(response).to be_successful - end - - it "contains the diaspora* ID" do - get :webfinger, format: :json, params: {resource: "acct:#{alice.diaspora_id}"} - expect(response.body).to include "\"subject\": \"acct:alice@localhost:3000\"" - end - - it "returns a application/jrd+json" do - get :webfinger, format: :json, params: {resource: "acct:#{alice.diaspora_id}"} - expect(response.header["Content-Type"]).to include "application/jrd+json" - end - - it "adds a Access-Control-Allow-Origin header" do - get :webfinger, format: :json, params: {resource: "acct:#{alice.diaspora_id}"} - expect(response.header["Access-Control-Allow-Origin"]).to eq("*") - end - - it "404s when the person does not exist" do - get :webfinger, format: :json, params: {resource: "me@mydiaspora.pod.com"} - expect(response).to be_not_found - end - - it "raises when the resource parameter is missing" do - expect { - get :webfinger, format: :json - }.to raise_error ActionController::ParameterMissing, /param is missing or the value is empty: resource/ - end - - it "calls the fetch_person_for_webfinger callback" do - expect_callback(:fetch_person_for_webfinger, "alice@localhost:3000").and_call_original - - get :webfinger, format: :json, params: {resource: "acct:alice@localhost:3000"} - end + it "succeeds when the person exists" do + get :webfinger, format: :json, params: {resource: alice.diaspora_id} + expect(response).to be_successful end - context "xml" do - it "succeeds when the person exists" do - get :webfinger, format: :json, params: {resource: alice.diaspora_id} - expect(response).to be_successful - end + it "succeeds with 'acct:' in the query when the person exists" do + get :webfinger, format: :json, params: {resource: "acct:#{alice.diaspora_id}"} + expect(response).to be_successful + end - it "succeeds with 'acct:' in the query when the person exists" do - get :webfinger, format: :xml, params: {resource: "acct:#{alice.diaspora_id}"} - expect(response).to be_successful - end + it "contains the diaspora* ID" do + get :webfinger, format: :json, params: {resource: "acct:#{alice.diaspora_id}"} + expect(response.body).to include "\"subject\": \"acct:alice@localhost:3000\"" + end - it "contains the diaspora* ID" do - get :webfinger, format: :xml, params: {resource: "acct:#{alice.diaspora_id}"} - expect(response.body).to include "acct:alice@localhost:3000" - end + it "returns a application/jrd+json" do + get :webfinger, format: :json, params: {resource: "acct:#{alice.diaspora_id}"} + expect(response.header["Content-Type"]).to include "application/jrd+json" + end - it "returns a application/xrd+xml" do - get :webfinger, format: :xml, params: {resource: "acct:#{alice.diaspora_id}"} - expect(response.header["Content-Type"]).to include "application/xrd+xml" - end + it "adds a Access-Control-Allow-Origin header" do + get :webfinger, format: :json, params: {resource: "acct:#{alice.diaspora_id}"} + expect(response.header["Access-Control-Allow-Origin"]).to eq("*") + end - it "404s when the person does not exist" do - get :webfinger, format: :xml, params: {resource: "me@mydiaspora.pod.com"} - expect(response).to be_not_found - end + it "404s when the person does not exist" do + get :webfinger, format: :json, params: {resource: "me@mydiaspora.pod.com"} + expect(response).to be_not_found + end - it "calls the fetch_person_for_webfinger callback" do - expect_callback(:fetch_person_for_webfinger, "alice@localhost:3000").and_call_original + it "raises when the resource parameter is missing" do + expect { + get :webfinger, format: :json + }.to raise_error ActionController::ParameterMissing, /param is missing or the value is empty: resource/ + end - get :webfinger, format: :xml, params: {resource: "acct:alice@localhost:3000"} - end + it "calls the fetch_person_for_webfinger callback" do + expect_callback(:fetch_person_for_webfinger, "alice@localhost:3000").and_call_original + + get :webfinger, format: :json, params: {resource: "acct:alice@localhost:3000"} end end end diff --git a/spec/lib/diaspora_federation/discovery/discovery_spec.rb b/spec/lib/diaspora_federation/discovery/discovery_spec.rb index d298d04..7c8ad03 100644 --- a/spec/lib/diaspora_federation/discovery/discovery_spec.rb +++ b/spec/lib/diaspora_federation/discovery/discovery_spec.rb @@ -2,7 +2,8 @@ module DiasporaFederation describe Discovery::Discovery do - let(:host_meta_xrd) { Discovery::HostMeta.from_base_url("http://localhost:3000/").to_xml } + subject(:discovery) { Discovery::Discovery.new(account) } + let(:webfinger_data) { { acct_uri: "acct:#{alice.diaspora_id}", @@ -17,9 +18,6 @@ module DiasporaFederation public_key: alice.serialized_public_key } } - let(:webfinger_xrd) { - DiasporaFederation::Discovery::WebFinger.new(webfinger_data).to_xml - } let(:webfinger_jrd) { JSON.pretty_generate(DiasporaFederation::Discovery::WebFinger.new(webfinger_data).to_json) } @@ -40,7 +38,6 @@ module DiasporaFederation } let(:account) { alice.diaspora_id } let(:default_image) { "http://localhost:3000/assets/user/default.png" } - subject { Discovery::Discovery.new(account) } describe "#intialize" do it "sets diaspora* ID" do @@ -62,7 +59,7 @@ module DiasporaFederation .to_return(status: 200, body: hcard_html) expect_callback(:save_person_after_webfinger, kind_of(Entities::Person)) - person = subject.fetch_and_save + person = discovery.fetch_and_save expect(person.guid).to eq(alice.guid) expect(person.diaspora_id).to eq(account) @@ -93,7 +90,7 @@ module DiasporaFederation callback_person = person end - expect(subject.fetch_and_save).to be(callback_person) + expect(discovery.fetch_and_save).to be(callback_person) end it "fails if the diaspora* ID does not match" do @@ -102,67 +99,32 @@ module DiasporaFederation stub_request(:get, "https://localhost:3000/.well-known/webfinger?resource=acct:#{account}") .to_return(status: 200, body: modified_webfinger) - expect { subject.fetch_and_save }.to raise_error Discovery::DiscoveryError + expect { discovery.fetch_and_save }.to raise_error Discovery::DiscoveryError end it "fails if the diaspora* ID was not found" do stub_request(:get, "https://localhost:3000/.well-known/webfinger?resource=acct:#{account}") .to_return(status: 404) - stub_request(:get, "http://localhost:3000/.well-known/webfinger?resource=acct:#{account}") - .to_return(status: 404) - stub_request(:get, "https://localhost:3000/.well-known/host-meta") - .to_return(status: 200, body: host_meta_xrd) - stub_request(:get, "http://localhost:3000/.well-known/webfinger.xml?resource=acct:#{account}") - .to_return(status: 404) - expect { subject.fetch_and_save }.to raise_error Discovery::DiscoveryError + expect { discovery.fetch_and_save }.to raise_error Discovery::DiscoveryError end - context "http fallback" do - context "http fallback disabled (default)" do - it "falls back to legacy WebFinger if https fails with 404" do - stub_request(:get, "https://localhost:3000/.well-known/webfinger?resource=acct:#{account}") - .to_return(status: 404) - stub_request(:get, "https://localhost:3000/.well-known/host-meta") - .to_return(status: 200, body: host_meta_xrd) - stub_request(:get, "http://localhost:3000/.well-known/webfinger.xml?resource=acct:#{account}") - .to_return(status: 200, body: webfinger_xrd) - stub_request(:get, "http://localhost:3000/hcard/users/#{alice.guid}") - .to_return(status: 200, body: hcard_html) - - expect_callback(:save_person_after_webfinger, kind_of(Entities::Person)) - person = subject.fetch_and_save - - expect(person.guid).to eq(alice.guid) - expect(person.diaspora_id).to eq(account) - end - - it "falls back to legacy WebFinger if https fails with ssl error" do + context "with http fallback" do + context "when http fallback disabled (default)" do + it "does not fall back to http if https fails with ssl error" do stub_request(:get, "https://localhost:3000/.well-known/webfinger?resource=acct:#{account}") .to_raise(OpenSSL::SSL::SSLError) - stub_request(:get, "https://localhost:3000/.well-known/host-meta") - .to_raise(OpenSSL::SSL::SSLError) - stub_request(:get, "http://localhost:3000/.well-known/host-meta") - .to_return(status: 200, body: host_meta_xrd) - stub_request(:get, "http://localhost:3000/.well-known/webfinger.xml?resource=acct:#{account}") - .to_return(status: 200, body: webfinger_xrd) - stub_request(:get, "http://localhost:3000/hcard/users/#{alice.guid}") - .to_return(status: 200, body: hcard_html) - expect_callback(:save_person_after_webfinger, kind_of(Entities::Person)) - person = subject.fetch_and_save - - expect(person.guid).to eq(alice.guid) - expect(person.diaspora_id).to eq(account) + expect { discovery.fetch_and_save }.to raise_error Discovery::DiscoveryError end end - context "http fallback enabled" do - before :all do + context "when http fallback enabled" do + before do DiasporaFederation.webfinger_http_fallback = true end - after :all do + after do DiasporaFederation.webfinger_http_fallback = false end @@ -175,7 +137,7 @@ module DiasporaFederation .to_return(status: 200, body: hcard_html) expect_callback(:save_person_after_webfinger, kind_of(Entities::Person)) - person = subject.fetch_and_save + person = discovery.fetch_and_save expect(person.guid).to eq(alice.guid) expect(person.diaspora_id).to eq(account) @@ -190,7 +152,7 @@ module DiasporaFederation .to_return(status: 200, body: hcard_html) expect_callback(:save_person_after_webfinger, kind_of(Entities::Person)) - person = subject.fetch_and_save + person = discovery.fetch_and_save expect(person.guid).to eq(alice.guid) expect(person.diaspora_id).to eq(account) @@ -198,118 +160,33 @@ module DiasporaFederation end end - context "legacy WebFinger" do - it "falls back to legacy WebFinger" do - incomplete_webfinger_json = "{\"links\":[{\"rel\":\"http://openid.net/specs/connect/1.0/issuer\"," \ - "\"href\":\"https://localhost:3000/\"}],\"subject\":\"acct:#{account}\"}" - stub_request(:get, "https://localhost:3000/.well-known/webfinger?resource=acct:#{account}") - .to_return(status: 200, body: incomplete_webfinger_json) - stub_request(:get, "https://localhost:3000/.well-known/host-meta") - .to_return(status: 200, body: host_meta_xrd) - stub_request(:get, "http://localhost:3000/.well-known/webfinger.xml?resource=acct:#{account}") - .to_return(status: 200, body: webfinger_xrd) - stub_request(:get, "http://localhost:3000/hcard/users/#{alice.guid}") - .to_return(status: 200, body: hcard_html) - - expect_callback(:save_person_after_webfinger, kind_of(Entities::Person)) - person = subject.fetch_and_save - - expect(person.guid).to eq(alice.guid) - expect(person.diaspora_id).to eq(account) - expect(person.url).to eq(alice.url) - expect(person.exported_key).to eq(alice.serialized_public_key) - - profile = person.profile - - expect(profile.diaspora_id).to eq(alice.diaspora_id) - expect(profile.first_name).to eq("Dummy") - expect(profile.last_name).to eq("User") - - expect(profile.image_url).to eq(default_image) - expect(profile.image_url_medium).to eq(default_image) - expect(profile.image_url_small).to eq(default_image) - end - - it "falls back to http if https fails with 404" do - stub_request(:get, "https://localhost:3000/.well-known/webfinger?resource=acct:#{account}") - .to_return(status: 404) - stub_request(:get, "https://localhost:3000/.well-known/host-meta") - .to_return(status: 404) - stub_request(:get, "http://localhost:3000/.well-known/host-meta") - .to_return(status: 200, body: host_meta_xrd) - stub_request(:get, "http://localhost:3000/.well-known/webfinger.xml?resource=acct:#{account}") - .to_return(status: 200, body: webfinger_xrd) - stub_request(:get, "http://localhost:3000/hcard/users/#{alice.guid}") - .to_return(status: 200, body: hcard_html) - - expect_callback(:save_person_after_webfinger, kind_of(Entities::Person)) - person = subject.fetch_and_save - - expect(person.guid).to eq(alice.guid) - expect(person.diaspora_id).to eq(account) - end - - it "falls back to http if https fails with ssl error" do - stub_request(:get, "https://localhost:3000/.well-known/webfinger?resource=acct:#{account}") - .to_raise(OpenSSL::SSL::SSLError) - stub_request(:get, "https://localhost:3000/.well-known/host-meta") - .to_raise(OpenSSL::SSL::SSLError) - stub_request(:get, "http://localhost:3000/.well-known/host-meta") - .to_return(status: 200, body: host_meta_xrd) - stub_request(:get, "http://localhost:3000/.well-known/webfinger.xml?resource=acct:#{account}") - .to_return(status: 200, body: webfinger_xrd) - stub_request(:get, "http://localhost:3000/hcard/users/#{alice.guid}") - .to_return(status: 200, body: hcard_html) - - expect_callback(:save_person_after_webfinger, kind_of(Entities::Person)) - person = subject.fetch_and_save - - expect(person.guid).to eq(alice.guid) - expect(person.diaspora_id).to eq(account) - end - - it "fails if the diaspora* ID does not match" do - modified_webfinger = webfinger_xrd.gsub(account, "anonther_user@example.com") - - stub_request(:get, "https://localhost:3000/.well-known/webfinger?resource=acct:#{account}") - .to_return(status: 200, body: "foobar") - stub_request(:get, "https://localhost:3000/.well-known/host-meta") - .to_return(status: 200, body: host_meta_xrd) - stub_request(:get, "http://localhost:3000/.well-known/webfinger.xml?resource=acct:#{account}") - .to_return(status: 200, body: modified_webfinger) - - expect { subject.fetch_and_save }.to raise_error Discovery::DiscoveryError - end - end - - context "error handling" do + context "with error handling" do it "re-raises DiscoveryError" do - expect(subject).to receive(:validate_diaspora_id) + expect(discovery).to receive(:validate_diaspora_id) .and_raise(Discovery::DiscoveryError, "Something went wrong!") - expect { subject.fetch_and_save }.to raise_error Discovery::DiscoveryError, "Something went wrong!" + expect { discovery.fetch_and_save }.to raise_error Discovery::DiscoveryError, "Something went wrong!" end it "re-raises InvalidDocument" do - expect(subject).to receive(:validate_diaspora_id) + expect(discovery).to receive(:validate_diaspora_id) .and_raise(Discovery::InvalidDocument, "Wrong document!") - expect { subject.fetch_and_save }.to raise_error Discovery::InvalidDocument, "Wrong document!" + expect { discovery.fetch_and_save }.to raise_error Discovery::InvalidDocument, "Wrong document!" end it "re-raises InvalidData" do - expect(subject).to receive(:validate_diaspora_id) + expect(discovery).to receive(:validate_diaspora_id) .and_raise(Discovery::InvalidData, "Wrong data!") - expect { subject.fetch_and_save }.to raise_error Discovery::InvalidData, "Wrong data!" + expect { discovery.fetch_and_save }.to raise_error Discovery::InvalidData, "Wrong data!" end it "raises a DiscoveryError when an unhandled error occurs" do - expect(subject).to receive(:validate_diaspora_id) - .and_raise("OMG! EVERYTHING IS BROKEN!") + allow(discovery).to receive(:validate_diaspora_id).and_raise("OMG! EVERYTHING IS BROKEN!") expect { - subject.fetch_and_save + discovery.fetch_and_save }.to raise_error Discovery::DiscoveryError, "Failed discovery for #{account}: RuntimeError: OMG! EVERYTHING IS BROKEN!" end diff --git a/spec/lib/diaspora_federation/discovery/host_meta_spec.rb b/spec/lib/diaspora_federation/discovery/host_meta_spec.rb deleted file mode 100644 index 0db73d5..0000000 --- a/spec/lib/diaspora_federation/discovery/host_meta_spec.rb +++ /dev/null @@ -1,103 +0,0 @@ -# frozen_string_literal: true - -module DiasporaFederation - describe Discovery::HostMeta do - let(:base_url) { "https://pod.example.tld/" } - let(:xml) { <<~XML } - - - - - XML - - it "must not create blank instances" do - expect { Discovery::HostMeta.new }.to raise_error NoMethodError - end - - context "generation" do - it "creates a nice XML document" do - hm = Discovery::HostMeta.from_base_url(base_url) - expect(hm.to_xml).to eq(xml) - end - - it "converts object to string" do - hm = Discovery::HostMeta.from_base_url(URI(base_url)) - expect(hm.to_xml).to eq(xml) - end - - it "appends a '/' if necessary" do - hm = Discovery::HostMeta.from_base_url("https://pod.example.tld") - expect(hm.to_xml).to eq(xml) - end - - it "fails if the base_url was omitted" do - expect { Discovery::HostMeta.from_base_url("") }.to raise_error Discovery::InvalidData - end - end - - context "parsing" do - it "parses its own output" do - hm = Discovery::HostMeta.from_xml(xml) - expect(hm.webfinger_template_url).to eq("#{base_url}.well-known/webfinger.xml?resource={uri}") - end - - it "also reads old-style XML" do - historic_xml = <<~XML - - - - - - - - - XML - hm = Discovery::HostMeta.from_xml(historic_xml) - expect(hm.webfinger_template_url).to eq("#{base_url}webfinger?q={uri}") - end - - it "also reads friendica/redmatrix XML" do - friendica_redmatrix_xml = <<~XML - - - - pod.example.tld - - - - - - XML - hm = Discovery::HostMeta.from_xml(friendica_redmatrix_xml) - expect(hm.webfinger_template_url).to eq("#{base_url}xrd/?uri={uri}") - end - - it "fails if the document does not contain a webfinger url" do - invalid_xml = <<~XML - - - - XML - expect { Discovery::HostMeta.from_xml(invalid_xml) }.to raise_error Discovery::InvalidData - end - - it "fails if the document contains a malformed webfinger url" do - invalid_xml = <<~XML - - - - - XML - expect { Discovery::HostMeta.from_xml(invalid_xml) }.to raise_error Discovery::InvalidData - end - - it "fails if the document is invalid" do - expect { Discovery::HostMeta.from_xml("") }.to raise_error Discovery::InvalidDocument - end - end - end -end diff --git a/spec/lib/diaspora_federation/discovery/web_finger_spec.rb b/spec/lib/diaspora_federation/discovery/web_finger_spec.rb index 1818e58..52dc4bd 100644 --- a/spec/lib/diaspora_federation/discovery/web_finger_spec.rb +++ b/spec/lib/diaspora_federation/discovery/web_finger_spec.rb @@ -18,29 +18,6 @@ module DiasporaFederation } } - let(:xml) { <<~XML } - - - #{acct} - #{person.alias_url} - - - - - - - - XML - - let(:minimal_xml) { <<~XML } - - - #{acct} - - - - XML - let(:json) { <<~JSON } { "subject": "#{acct}", @@ -74,7 +51,7 @@ module DiasporaFederation }, { "rel": "http://ostatus.org/schema/1.0/subscribe", - "template": "http://somehost:3000/people?q={uri}" + "template": "#{person.url}people?q={uri}" } ] } @@ -102,7 +79,7 @@ module DiasporaFederation it_behaves_like "an Entity subclass" - context "generation" do + context "when generating" do let(:minimal_data) { {acct_uri: acct, hcard_url: person.hcard_url, seed_url: person.url} } let(:additional_data) { { @@ -120,113 +97,66 @@ module DiasporaFederation } } - context "xml" do - it "creates a nice XML document" do - wf = Discovery::WebFinger.new(data, aliases: [person.alias_url]) - expect(wf.to_xml).to eq(xml) - end - - it "creates minimal XML document" do - wf = Discovery::WebFinger.new(minimal_data) - expect(wf.to_xml).to eq(minimal_xml) - end - - it "creates XML document with additional data" do - xml_with_additional_data = <<~XML - - - #{acct} - #{person.alias_url} - #{person.profile_url} - Bob Smith - - - - - - - XML - - wf = Discovery::WebFinger.new(minimal_data, additional_data) - expect(wf.to_xml).to eq(xml_with_additional_data) - end + it "creates a nice JSON document" do + wf = Discovery::WebFinger.new(data, aliases: [person.alias_url]) + expect(JSON.pretty_generate(wf.to_json)).to eq(json.strip) end - context "json" do - it "creates a nice JSON document" do - wf = Discovery::WebFinger.new(data, aliases: [person.alias_url]) - expect(JSON.pretty_generate(wf.to_json)).to eq(json.strip) - end + it "creates minimal JSON document" do + wf = Discovery::WebFinger.new(minimal_data) + expect(JSON.pretty_generate(wf.to_json)).to eq(minimal_json.strip) + end - it "creates minimal JSON document" do - wf = Discovery::WebFinger.new(minimal_data) - expect(JSON.pretty_generate(wf.to_json)).to eq(minimal_json.strip) - end - - it "creates JSON document with additional data" do - json_with_additional_data = <<~JSON - { - "subject": "#{acct}", - "aliases": [ - "#{person.alias_url}", - "#{person.profile_url}" - ], - "properties": { - "http://webfinger.example/ns/name": "Bob Smith" + it "creates JSON document with additional data" do + json_with_additional_data = <<~JSON + { + "subject": "#{acct}", + "aliases": [ + "#{person.alias_url}", + "#{person.profile_url}" + ], + "properties": { + "http://webfinger.example/ns/name": "Bob Smith" + }, + "links": [ + { + "rel": "http://microformats.org/profile/hcard", + "type": "text/html", + "href": "#{person.hcard_url}" }, - "links": [ - { - "rel": "http://microformats.org/profile/hcard", - "type": "text/html", - "href": "#{person.hcard_url}" - }, - { - "rel": "http://joindiaspora.com/seed_location", - "type": "text/html", - "href": "#{person.url}" - }, - { - "rel": "http://portablecontacts.net/spec/1.0", - "href": "https://pod.example.tld/poco/trouble" - }, - { - "rel": "http://webfinger.net/rel/avatar", - "type": "image/jpeg", - "href": "http://localhost:3000/assets/user/default.png" - }, - { - "rel": "http://openid.net/specs/connect/1.0/issuer", - "href": "https://pod.example.tld/" - } - ] - } - JSON + { + "rel": "http://joindiaspora.com/seed_location", + "type": "text/html", + "href": "#{person.url}" + }, + { + "rel": "http://portablecontacts.net/spec/1.0", + "href": "https://pod.example.tld/poco/trouble" + }, + { + "rel": "http://webfinger.net/rel/avatar", + "type": "image/jpeg", + "href": "http://localhost:3000/assets/user/default.png" + }, + { + "rel": "http://openid.net/specs/connect/1.0/issuer", + "href": "https://pod.example.tld/" + } + ] + } + JSON - wf = Discovery::WebFinger.new(minimal_data, additional_data) - expect(JSON.pretty_generate(wf.to_json)).to eq(json_with_additional_data.strip) - end + wf = Discovery::WebFinger.new(minimal_data, additional_data) + expect(JSON.pretty_generate(wf.to_json)).to eq(json_with_additional_data.strip) + end + + it "does not support XML anymore" do + expect { Discovery::WebFinger.new(minimal_data).to_xml } + .to raise_error "Generating WebFinger to XML is not supported anymore, use 'to_json' instead." end end - context "parsing" do - it "reads its own xml output" do - wf = Discovery::WebFinger.from_xml(xml) - expect(wf.acct_uri).to eq(acct) - expect(wf.hcard_url).to eq(person.hcard_url) - expect(wf.seed_url).to eq(person.url) - expect(wf.profile_url).to eq(person.profile_url) - expect(wf.atom_url).to eq(person.atom_url) - expect(wf.salmon_url).to eq(person.salmon_url) - expect(wf.subscribe_url).to eq(person.subscribe_url) - end - - it "reads minimal xml" do - wf = Discovery::WebFinger.from_xml(minimal_xml) - expect(wf.acct_uri).to eq(acct) - expect(wf.hcard_url).to eq(person.hcard_url) - expect(wf.seed_url).to eq(person.url) - end - + context "when parsing" do it "reads its own json output" do wf = Discovery::WebFinger.from_json(json) expect(wf.acct_uri).to eq(acct) @@ -246,119 +176,219 @@ module DiasporaFederation end it "is frozen after parsing" do - wf = Discovery::WebFinger.from_xml(xml) + wf = Discovery::WebFinger.from_json(json) expect(wf).to be_frozen end - it "reads friendica XML (two aliases, first with acct)" do - friendica_xml = <<~XML - - + it "reads friendica JSON" do + friendica_hcard_url = "#{person.url}hcard/#{person.nickname}" + friendica_profile_url = "#{person.url}profile/#{person.nickname}" + friendica_atom_url = "#{person.url}dfrn_poll/#{person.nickname}" + friendica_salmon_url = "#{person.url}salmon/#{person.nickname}" + friendica_subscribe_url = "#{person.url}follow?url={uri}" - #{acct} - #{acct} - #{person.alias_url} + friendica_json = <<~JSON + { + "subject": "#{acct}", + "aliases": [ + "#{person.url}~#{person.nickname}", + "#{friendica_profile_url}" + ], + "links": [ + { + "rel": "http://purl.org/macgirvin/dfrn/1.0", + "href": "#{person.url}profile/#{person.nickname}" + }, + { + "rel": "http://schemas.google.com/g/2010#updates-from", + "type": "application/atom+xml", + "href": "#{friendica_atom_url}" + }, + { + "rel": "http://webfinger.net/rel/profile-page", + "type": "text/html", + "href": "#{friendica_profile_url}" + }, + { + "rel": "self", + "type": "application/activity+json", + "href": "#{friendica_profile_url}" + }, + { + "rel": "http://microformats.org/profile/hcard", + "type": "text/html", + "href": "#{friendica_hcard_url}" + }, + { + "rel": "http://portablecontacts.net/spec/1.0", + "href": "#{person.url}poco/#{person.nickname}" + }, + { + "rel": "http://webfinger.net/rel/avatar", + "type": "image/png", + "href": "#{person.url}photo/profile/#{person.nickname}.png" + }, + { + "rel": "http://joindiaspora.com/seed_location", + "type": "text/html", + "href": "#{person.url}" + }, + { + "rel": "salmon", + "href": "#{friendica_salmon_url}" + }, + { + "rel": "http://salmon-protocol.org/ns/salmon-replies", + "href": "#{friendica_salmon_url}" + }, + { + "rel": "http://salmon-protocol.org/ns/salmon-mention", + "href": "#{friendica_salmon_url}/mention" + }, + { + "rel": "http://ostatus.org/schema/1.0/subscribe", + "template": "#{person.url}follow?url={uri}" + }, + { + "rel": "magic-public-key", + "href": "data:application/magic-public-key,RSA.abcdef1234567890" + }, + { + "rel": "http://purl.org/openwebauth/v1", + "type": "application/x-zot+json", + "href": "#{person.url}owa" + } + ] + } + JSON - - - - - - - - - - - - - - - - - RSA.abcdef1234567890 - - - XML - - wf = Discovery::WebFinger.from_xml(friendica_xml) + wf = Discovery::WebFinger.from_json(friendica_json) expect(wf.acct_uri).to eq(acct) - expect(wf.hcard_url).to eq(person.hcard_url) + expect(wf.hcard_url).to eq(friendica_hcard_url) expect(wf.seed_url).to eq(person.url) - expect(wf.profile_url).to eq(person.profile_url) - expect(wf.atom_url).to eq(person.atom_url) - expect(wf.salmon_url).to eq(person.salmon_url) - expect(wf.subscribe_url).to eq("https://pod.example.tld/follow?url={uri}") + expect(wf.profile_url).to eq(friendica_profile_url) + expect(wf.atom_url).to eq(friendica_atom_url) + expect(wf.salmon_url).to eq(friendica_salmon_url) + expect(wf.subscribe_url).to eq(friendica_subscribe_url) end - it "reads redmatrix XML (no alias)" do - redmatrix_xml = <<~XML - - + it "reads hubzilla JSON" do + hubzilla_hcard_url = "#{person.url}hcard/#{person.nickname}" + hubzilla_profile_url = "#{person.url}profile/#{person.nickname}" + hubzilla_atom_url = "#{person.url}ofeed/#{person.nickname}" + hubzilla_subscribe_url = "#{person.url}follow?f=&url={uri}" - #{person.diaspora_id} + hubzilla_json = <<~JSON + { + "subject": "#{acct}", + "aliases": [ + "#{person.url}channel/#{person.nickname}", + "#{person.url}~#{person.nickname}", + "#{person.url}@#{person.nickname}" + ], + "properties": { + "http://webfinger.net/ns/name": "#{person.full_name}", + "http://xmlns.com/foaf/0.1/name": "#{person.full_name}", + "https://w3id.org/security/v1#publicKeyPem": #{person.serialized_public_key.dump}, + "http://purl.org/zot/federation": "zot6,zot,activitypub,diaspora" + }, + "links": [ + { + "rel": "http://webfinger.net/rel/avatar", + "type": "image/jpeg", + "href": "#{person.url}photo/profile/l/2" + }, + { + "rel": "http://microformats.org/profile/hcard", + "type": "text/html", + "href": "#{hubzilla_hcard_url}" + }, + { + "rel": "http://openid.net/specs/connect/1.0/issuer", + "href": "#{person.url}" + }, + { + "rel": "http://webfinger.net/rel/profile-page", + "href": "#{hubzilla_profile_url}" + }, + { + "rel": "http://schemas.google.com/g/2010#updates-from", + "type": "application/atom+xml", + "href": "#{hubzilla_atom_url}" + }, + { + "rel": "http://webfinger.net/rel/blog", + "href": "#{person.url}channel/#{person.nickname}" + }, + { + "rel": "http://ostatus.org/schema/1.0/subscribe", + "template": "#{hubzilla_subscribe_url}" + }, + { + "rel": "http://purl.org/zot/protocol/6.0", + "type": "application/x-zot+json", + "href": "#{person.url}channel/#{person.nickname}" + }, + { + "rel": "http://purl.org/zot/protocol", + "href": "#{person.url}.well-known/zot-info?address=#{person.nickname}@#{person.diaspora_id.split('@')[1]}" + }, + { + "rel": "http://purl.org/openwebauth/v1", + "type": "application/x-zot+json", + "href": "#{person.url}owa" + }, + { + "rel": "magic-public-key", + "href": "data:application/magic-public-key,RSA.abcdef1234567890" + }, + { + "rel": "self", + "type": "application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"", + "href": "#{person.url}channel/#{person.nickname}" + }, + { + "rel": "self", + "type": "application/activity+json", + "href": "#{person.url}channel/#{person.nickname}" + }, + { + "rel": "http://joindiaspora.com/seed_location", + "type": "text/html", + "href": "#{person.url}" + }, + { + "rel": "salmon", + "href": "#{person.salmon_url}" + } + ] + } + JSON - - - - - - - - - - - - - - XML - - wf = Discovery::WebFinger.from_xml(redmatrix_xml) - expect(wf.acct_uri).to eq(person.diaspora_id) - expect(wf.hcard_url).to eq(person.hcard_url) + wf = Discovery::WebFinger.from_json(hubzilla_json) + expect(wf.acct_uri).to eq(acct) + expect(wf.hcard_url).to eq(hubzilla_hcard_url) expect(wf.seed_url).to eq(person.url) - expect(wf.profile_url).to eq(person.profile_url) - expect(wf.atom_url).to eq(person.atom_url) - expect(wf.salmon_url).to be_nil + expect(wf.profile_url).to eq(hubzilla_profile_url) + expect(wf.atom_url).to eq(hubzilla_atom_url) + expect(wf.salmon_url).to eq(person.salmon_url) + expect(wf.subscribe_url).to eq(hubzilla_subscribe_url) end it "fails if the document is empty" do - invalid_xml = <<~XML - - - - XML - expect { Discovery::WebFinger.from_xml(invalid_xml) }.to raise_error Discovery::InvalidData + invalid_json = <<~JSON + {} + JSON + expect { Discovery::WebFinger.from_json(invalid_json) }.to raise_error Discovery::InvalidData end - it "fails if the document is not XML" do - expect { Discovery::WebFinger.from_xml("") }.to raise_error Discovery::InvalidDocument + it "fails if the document is not JSON" do + expect { Discovery::WebFinger.from_json("") }.to raise_error Discovery::InvalidDocument + end + + it "does not support XML anymore" do + expect { Discovery::WebFinger.from_xml("") } + .to raise_error "Parsing WebFinger as XML is not supported anymore, use 'from_json' instead." end end end diff --git a/spec/routing/webfinger_routing_spec.rb b/spec/routing/webfinger_routing_spec.rb index 9b1f33b..c4bb08e 100644 --- a/spec/routing/webfinger_routing_spec.rb +++ b/spec/routing/webfinger_routing_spec.rb @@ -4,13 +4,6 @@ module DiasporaFederation describe ReceiveController, type: :routing do routes { DiasporaFederation::Engine.routes } - it "routes GET host-meta" do - expect(get: ".well-known/host-meta").to route_to( - controller: "diaspora_federation/webfinger", - action: "host_meta" - ) - end - it "routes GET webfinger" do expect(get: "/.well-known/webfinger").to route_to( controller: "diaspora_federation/webfinger",