diff --git a/docs/discovery/webfinger.md b/docs/discovery/webfinger.md index b2044f2..123eeda 100644 --- a/docs/discovery/webfinger.md +++ b/docs/discovery/webfinger.md @@ -2,88 +2,41 @@ title: WebFinger --- -diaspora\* uses an old draft of [WebFinger][webfinger-draft] to discover users from other pods. - -{% include warning_box.html - title="Old WebFinger" - content="

diaspora* doesn't yet fully support the RFC 7033 WebFinger!

" -%} - -## WebFinger endpoint discovery - -To find the WebFinger endpoint, the requesting server must get the [host metadata][host-meta] information for the -domain of the searched diaspora\* ID. - -### Request - -Let's assume we are searching for `alice@example.org`, then we need the host metadata for `example.org`. - -The request should first be tried with https, if this doesn't work, the requesting server should fallback to http. - -If the server response indicates that the host-meta resource is located elsewhere (a 301, 302, or 307 response status -code), the requesting server should try to obtain the resource from the location provided in the response. - -#### Example - -~~~ -GET /.well-known/host-meta -Host: example.org -~~~ - -### Response - -The host-meta document must be in the [XRD 1.0 document format][xrd] and should be served with the -`application/xrd+xml` media type. - -#### Mandatory Link Relations - -The Host Metadata response must contain the following link relations: - -| Link Relation | Type | Description | -| ------------- | ------------ | ------------------------------------------------------------------------- | -| lrdd | url template | The template to the URL where the server provides the WebFinger endpoint. | - -The Host Metadata response may contain other optional link relations. - -#### Example - -~~~ -Status: 200 OK -Content-Type: application/xrd+xml; charset=utf-8 -~~~ -~~~xml - - - - -~~~ +diaspora\* uses [WebFinger][webfinger-rfc] to discover users from other pods. ## WebFinger ### Request +~~~ +GET /.well-known/webfinger +~~~ + +Let's assume we are searching for `alice@example.org`, then we need to make a request to `example.org`. + #### Parameters -The requesting server must replace the ``{uri}`` in the lrdd-template from the host-meta request with the diaspora\* ID -of the searched person. +| Name | Description | +| ---------- | ------------------------------------------------------------ | +| `resource` | The "acct" URI for the diaspora\* ID of the searched person. | #### Example ~~~ -GET /.well-known/webfinger.xml?resource=acct:alice@example.org +GET /.well-known/webfinger?resource=acct:alice@example.org Host: example.org ~~~ ### Response -The webfinger document must be in the [XRD 1.0 document format][xrd] and should be served with the -`application/xrd+xml` media type. +The WebFinger document must be in the [JSON Resource Descriptor (JRD) format][jrd] and should be served with the +`application/jrd+json` media type. If the requested diaspora\* ID is unknown by the server, it must return a 404 status code. #### Subject -The ``Subject`` element should contain the webfinger address that was asked for. If it does not, then this webfinger +The `subject` element should contain the WebFinger address that was asked for. If it does not, then this WebFinger profile must be ignored. #### Mandatory Link Relations @@ -101,26 +54,32 @@ The WebFinger response may contain other optional link relations. ~~~ Status: 200 OK -Content-Type: application/xrd+xml; charset=utf-8 +Content-Type: application/jrd+json; charset=utf-8 +Access-Control-Allow-Origin: * ~~~ -~~~xml - - - acct:alice@example.org - - - +~~~json +{ + "subject": "acct:alice@example.org", + "links": [ + { + "rel": "http://microformats.org/profile/hcard", + "type": "text/html", + "href": "https://example.org/hcard/users/7dba7ca01d64013485eb3131731751e9" + }, + { + "rel": "http://joindiaspora.com/seed_location", + "type": "text/html", + "href": "https://example.org/" + } + ] +} ~~~ ## Additional information and specifications -* [RFC 6415: Web Host Metadata][host-meta] -* [WebFinger draft][webfinger-draft] -* [Extensible Resource Descriptor (XRD) Version 1.0][xrd] * [RFC 7033: WebFinger][webfinger-rfc] +* [JSON Resource Descriptor (JRD)][jrd] -[host-meta]: https://tools.ietf.org/html/rfc6415 -[webfinger-draft]: https://tools.ietf.org/html/draft-jones-appsawg-webfinger-06 [webfinger-rfc]: https://tools.ietf.org/html/rfc7033 -[xrd]: http://docs.oasis-open.org/xri/xrd/v1.0/xrd-1.0.html +[jrd]: https://www.packetizer.com/json/jrd/ [hcard]: {{ site.baseurl }}/discovery/hcard.html diff --git a/lib/diaspora_federation.rb b/lib/diaspora_federation.rb index 5ba21df..08c360a 100644 --- a/lib/diaspora_federation.rb +++ b/lib/diaspora_federation.rb @@ -39,6 +39,7 @@ module DiasporaFederation ] # defaults + @webfinger_http_fallback = false @http_concurrency = 20 @http_timeout = 30 @http_verbose = false @@ -74,6 +75,21 @@ module DiasporaFederation # @param [String] value path to certificate authorities attr_accessor :certificate_authorities + # Configure if WebFinger discovery should fallback to http if https fails (default: +false+) + # + # This is useful for example for development environments where https isn't available. + # + # This setting only applies to the WebFinger route from RFC 7033 +/.well-known/webfinger+. + # Legacy WebFinger flow unconditionally falls back to http. + # + # @overload webfinger_http_fallback + # @return [Boolean] webfinger http fallback enabled + # @overload webfinger_http_fallback= + # @example + # config.webfinger_http_fallback = AppConfig.server.rails_environment == "development" + # @param [Boolean] value webfinger http fallback enabled + attr_accessor :webfinger_http_fallback + # Maximum number of parallel HTTP requests made to other pods (default: +20+) # # @overload http_concurrency diff --git a/lib/diaspora_federation/discovery/discovery.rb b/lib/diaspora_federation/discovery/discovery.rb index 743da34..8fbfad4 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 if webfinger_http_fallback is enabled. + @webfinger = WebFinger.from_json(get(webfinger_url, DiasporaFederation.webfinger_http_fallback)) + 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/lib/diaspora_federation/discovery/web_finger.rb b/lib/diaspora_federation/discovery/web_finger.rb index 70f13f1..c16f634 100644 --- a/lib/diaspora_federation/discovery/web_finger.rb +++ b/lib/diaspora_federation/discovery/web_finger.rb @@ -119,8 +119,20 @@ module DiasporaFederation # @return [WebFinger] WebFinger instance # @raise [InvalidData] if the given XML string is invalid or incomplete def self.from_xml(webfinger_xml) - data = parse_xml_and_validate(webfinger_xml) + from_hash(parse_xml_and_validate(webfinger_xml)) + 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)) + end + + # Creates a WebFinger instance from the given data + # @param [Hash] data WebFinger data hash + # @return [WebFinger] WebFinger instance + def self.from_hash(data) links = data[:links] new( diff --git a/lib/diaspora_federation/discovery/xrd_document.rb b/lib/diaspora_federation/discovery/xrd_document.rb index a77bf8c..354594c 100644 --- a/lib/diaspora_federation/discovery/xrd_document.rb +++ b/lib/diaspora_federation/discovery/xrd_document.rb @@ -83,10 +83,10 @@ module DiasporaFederation def to_json { subject: subject, - expires: expires, + expires: (expires.strftime(DATETIME_FORMAT) if expires.instance_of?(DateTime)), aliases: (aliases if aliases.any?), - links: (links if links.any?), - properties: (properties if properties.any?) + properties: (properties if properties.any?), + links: (links if links.any?) }.reject {|_, v| v.nil? } end @@ -115,6 +115,27 @@ module DiasporaFederation end end + # Parse the JRD document from the given string and create a hash containing + # the extracted data with symbolized keys. + # + # @param [String] jrd_doc JSON string + # @return [Hash] extracted data + # @raise [InvalidDocument] if the JRD is malformed + def self.json_data(jrd_doc) + json_hash = JSON.parse(jrd_doc) + + { + subject: json_hash["subject"], + expires: (DateTime.strptime(json_hash["expires"], DATETIME_FORMAT) if json_hash.key?("expires")), + aliases: json_hash["aliases"], + properties: json_hash["properties"], + links: symbolize_keys_for_links(json_hash["links"]) + }.reject {|_, v| v.nil? } + rescue JSON::JSONError => e + raise InvalidDocument, + "Not a JRD document: #{e.class}: #{e.message[0..255].encode(Encoding.default_external, undef: :replace)}" + end + private attr_reader :expires @@ -180,6 +201,17 @@ module DiasporaFederation end data[:links] = links unless links.empty? end + + # 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| + {}.tap do |hash| + LINK_ATTRS.each do |attr| + hash[attr] = link[attr.to_s] if link.key?(attr.to_s) + end + end + end + end end end end diff --git a/spec/lib/diaspora_federation/discovery/discovery_spec.rb b/spec/lib/diaspora_federation/discovery/discovery_spec.rb index 7d0c4cf..7ee024e 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,196 @@ module DiasporaFederation callback_person = person end - expect(Discovery::Discovery.new(account).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") - .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 = Discovery::Discovery.new(account).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") - .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 = Discovery::Discovery.new(account).fetch_and_save - - expect(person.guid).to eq(alice.guid) - expect(person.diaspora_id).to eq(account) + expect(subject.fetch_and_save).to be(callback_person) 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 "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 + 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 + end + + context "http fallback enabled" do + before :all do + DiasporaFederation.webfinger_http_fallback = true + end + + after :all do + DiasporaFederation.webfinger_http_fallback = false + 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: 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 = 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: 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 = subject.fetch_and_save + + expect(person.guid).to eq(alice.guid) + expect(person.diaspora_id).to eq(account) + end + 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 - subject { Discovery::Discovery.new(account) } - it "re-raises DiscoveryError" do expect(subject).to receive(:validate_diaspora_id) .and_raise(Discovery::DiscoveryError, "Something went wrong!") diff --git a/spec/lib/diaspora_federation/discovery/web_finger_spec.rb b/spec/lib/diaspora_federation/discovery/web_finger_spec.rb index ce2a612..fb0a2a4 100644 --- a/spec/lib/diaspora_federation/discovery/web_finger_spec.rb +++ b/spec/lib/diaspora_federation/discovery/web_finger_spec.rb @@ -39,6 +39,63 @@ XML XML + let(:json) { <<-JSON } +{ + "subject": "#{acct}", + "aliases": [ + "#{person.alias_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://webfinger.net/rel/profile-page", + "type": "text/html", + "href": "#{person.profile_url}" + }, + { + "rel": "http://schemas.google.com/g/2010#updates-from", + "type": "application/atom+xml", + "href": "#{person.atom_url}" + }, + { + "rel": "salmon", + "href": "#{person.salmon_url}" + }, + { + "rel": "http://ostatus.org/schema/1.0/subscribe", + "template": "http://somehost:3000/people?q={uri}" + } + ] +} +JSON + + let(:minimal_json) { <<-JSON } +{ + "subject": "#{acct}", + "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}" + } + ] +} +JSON + let(:string) { "WebFinger:#{data[:acct_uri]}" } it_behaves_like "an Entity subclass" @@ -95,66 +152,11 @@ XML context "json" do it "creates a nice JSON document" do - json = <<-JSON -{ - "subject": "#{acct}", - "aliases": [ - "#{person.alias_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://webfinger.net/rel/profile-page", - "type": "text/html", - "href": "#{person.profile_url}" - }, - { - "rel": "http://schemas.google.com/g/2010#updates-from", - "type": "application/atom+xml", - "href": "#{person.atom_url}" - }, - { - "rel": "salmon", - "href": "#{person.salmon_url}" - }, - { - "rel": "http://ostatus.org/schema/1.0/subscribe", - "template": "http://somehost:3000/people?q={uri}" - } - ] -} -JSON 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 - minimal_json = <<-JSON -{ - "subject": "#{acct}", - "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}" - } - ] -} -JSON wf = Discovery::WebFinger.new(minimal_data) expect(JSON.pretty_generate(wf.to_json)).to eq(minimal_json.strip) end @@ -167,6 +169,9 @@ JSON "#{person.alias_url}", "#{person.profile_url}" ], + "properties": { + "http://webfinger.example/ns/name": "Bob Smith" + }, "links": [ { "rel": "http://microformats.org/profile/hcard", @@ -191,10 +196,7 @@ JSON "rel": "http://openid.net/specs/connect/1.0/issuer", "href": "https://pod.example.tld/" } - ], - "properties": { - "http://webfinger.example/ns/name": "Bob Smith" - } + ] } JSON @@ -205,7 +207,7 @@ JSON end context "parsing" do - it "reads its own output" 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) @@ -223,6 +225,24 @@ JSON expect(wf.seed_url).to eq(person.url) end + it "reads its own json output" do + wf = Discovery::WebFinger.from_json(json) + 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 json" do + wf = Discovery::WebFinger.from_json(minimal_json) + 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 + it "is frozen after parsing" do wf = Discovery::WebFinger.from_xml(xml) expect(wf).to be_frozen diff --git a/spec/lib/diaspora_federation/discovery/xrd_document_spec.rb b/spec/lib/diaspora_federation/discovery/xrd_document_spec.rb index 5c885cb..3fe8a3b 100644 --- a/spec/lib/diaspora_federation/discovery/xrd_document_spec.rb +++ b/spec/lib/diaspora_federation/discovery/xrd_document_spec.rb @@ -15,6 +15,36 @@ module DiasporaFederation XML + let(:json) { <<-JSON } +{ + "subject": "http://blog.example.com/article/id/314", + "expires": "2010-01-30T09:30:00Z", + "aliases": [ + "http://blog.example.com/cool_new_thing", + "http://blog.example.com/steve/article/7" + ], + "properties": { + "http://blgx.example.net/ns/version": "1.3", + "http://blgx.example.net/ns/ext": null + }, + "links": [ + { + "rel": "author", + "type": "text/html", + "href": "http://blog.example.com/author/steve" + }, + { + "rel": "author", + "href": "http://example.com/author/john" + }, + { + "rel": "copyright", + "template": "http://example.com/copyright?id={uri}" + } + ] +} +JSON + let(:data) { { subject: "http://blog.example.com/article/id/314", @@ -72,7 +102,7 @@ XML describe "#to_json" do it "provides the hash for json" do - expect(doc.to_json).to eq(data) + expect(JSON.pretty_generate(doc.to_json)).to eq(json.strip) end end @@ -90,5 +120,16 @@ XML expect { Discovery::XrdDocument.xml_data("") }.to raise_error Discovery::InvalidDocument end end + + describe ".json_data" do + it "reads the json document" do + hash = Discovery::XrdDocument.json_data(json) + expect(hash).to eq(data) + end + + it "raises InvalidDocument when a JSON error occurs" do + expect { Discovery::XrdDocument.json_data("foo") }.to raise_error Discovery::InvalidDocument + end + end end end