From 3e7e6490257c9517437d624353e11234279f3d63 Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Sun, 27 Aug 2017 01:23:05 +0200 Subject: [PATCH] Fetch RFC 7033 WebFinger with fallback to legacy WebFinger --- .../discovery/discovery.rb | 24 ++- .../discovery/discovery_spec.rb | 153 ++++++++++++++---- 2 files changed, 137 insertions(+), 40 deletions(-) diff --git a/lib/diaspora_federation/discovery/discovery.rb b/lib/diaspora_federation/discovery/discovery.rb index 743da34..f46a860 100644 --- a/lib/diaspora_federation/discovery/discovery.rb +++ b/lib/diaspora_federation/discovery/discovery.rb @@ -58,23 +58,33 @@ module DiasporaFederation retry end - def host_meta_url - domain = diaspora_id.split("@")[1] - "https://#{domain}/.well-known/host-meta" + def domain + @domain ||= diaspora_id.split("@")[1] + end + + def acct_parameter + "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(host_meta_url, true) - host_meta.webfinger_template_url.gsub("{uri}", "acct:#{diaspora_id}") + host_meta = HostMeta.from_xml(get("https://#{domain}/.well-known/host-meta", true)) + host_meta.webfinger_template_url.gsub("{uri}", acct_parameter) end def webfinger - @webfinger ||= WebFinger.from_xml get(legacy_webfinger_url_from_host_meta) + 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. + @webfinger = WebFinger.from_json(get(webfinger_url, true)) + rescue => e + 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)) end def hcard - @hcard ||= HCard.from_html get(webfinger.hcard_url) + @hcard ||= HCard.from_html(get(webfinger.hcard_url)) end def person diff --git a/spec/lib/diaspora_federation/discovery/discovery_spec.rb b/spec/lib/diaspora_federation/discovery/discovery_spec.rb index 7d0c4cf..ddac373 100644 --- a/spec/lib/diaspora_federation/discovery/discovery_spec.rb +++ b/spec/lib/diaspora_federation/discovery/discovery_spec.rb @@ -1,8 +1,8 @@ module DiasporaFederation describe Discovery::Discovery do let(:host_meta_xrd) { Discovery::HostMeta.from_base_url("http://localhost:3000/").to_xml } - let(:webfinger_xrd) { - DiasporaFederation::Discovery::WebFinger.new( + let(:webfinger_data) { + { acct_uri: "acct:#{alice.diaspora_id}", alias_url: alice.alias_url, hcard_url: alice.hcard_url, @@ -13,7 +13,13 @@ module DiasporaFederation subscribe_url: alice.subscribe_url, guid: alice.guid, public_key: alice.serialized_public_key - ).to_xml + } + } + 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) } let(:hcard_html) { DiasporaFederation::Discovery::HCard.new( @@ -32,6 +38,7 @@ 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 @@ -47,15 +54,13 @@ module DiasporaFederation describe ".fetch_and_save" do it "fetches the userdata and returns a person object" do - 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, "https://localhost:3000/.well-known/webfinger?resource=acct:#{account}") + .to_return(status: 200, body: webfinger_jrd) 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 = Discovery::Discovery.new(account).fetch_and_save + person = subject.fetch_and_save expect(person.guid).to eq(alice.guid) expect(person.diaspora_id).to eq(account) @@ -74,10 +79,8 @@ module DiasporaFederation end it "fetches the userdata and saves the person object via callback" do - 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, "https://localhost:3000/.well-known/webfinger?resource=acct:#{account}") + .to_return(status: 200, body: webfinger_jrd) stub_request(:get, "http://localhost:3000/hcard/users/#{alice.guid}") .to_return(status: 200, body: hcard_html) @@ -88,66 +91,150 @@ module DiasporaFederation callback_person = person end - expect(Discovery::Discovery.new(account).fetch_and_save).to be(callback_person) + expect(subject.fetch_and_save).to be(callback_person) end it "falls back to http if https fails with 404" do - stub_request(:get, "https://localhost:3000/.well-known/host-meta") + stub_request(:get, "https://localhost:3000/.well-known/webfinger?resource=acct:#{account}") .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/.well-known/webfinger?resource=acct:#{account}") + .to_return(status: 200, body: webfinger_jrd) 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 = Discovery::Discovery.new(account).fetch_and_save + 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/host-meta") + stub_request(:get, "https://localhost:3000/.well-known/webfinger?resource=acct:#{account}") .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/.well-known/webfinger?resource=acct:#{account}") + .to_return(status: 200, body: webfinger_jrd) 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 = Discovery::Discovery.new(account).fetch_and_save + 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") + modified_webfinger = webfinger_jrd.gsub(account, "anonther_user@example.com") - 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}") + stub_request(:get, "https://localhost:3000/.well-known/webfinger?resource=acct:#{account}") .to_return(status: 200, body: modified_webfinger) - expect { Discovery::Discovery.new(account).fetch_and_save }.to raise_error Discovery::DiscoveryError + expect { subject.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 { Discovery::Discovery.new(account).fetch_and_save }.to raise_error Discovery::DiscoveryError + expect { subject.fetch_and_save }.to raise_error Discovery::DiscoveryError + 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, "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: 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, "http://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_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 - subject { Discovery::Discovery.new(account) } - it "re-raises DiscoveryError" do expect(subject).to receive(:validate_diaspora_id) .and_raise(Discovery::DiscoveryError, "Something went wrong!")