Add support to parse RFC 7033 WebFinger JSON

Also:
* Fix date format when generating JRD document
* Sort elements always with the same order
This commit is contained in:
Benjamin Neff 2017-08-26 23:47:19 +02:00
parent 9d72c9855a
commit 6852f9ca36
No known key found for this signature in database
GPG key ID: 971464C3F1A90194
4 changed files with 170 additions and 65 deletions

View file

@ -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(

View file

@ -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

View file

@ -39,6 +39,63 @@ XML
</XRD>
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

View file

@ -15,6 +15,36 @@ module DiasporaFederation
</XRD>
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("<html></html>") }.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