Implement RFC 7033, bump version to 1.0.0, add some documentation

This commit is contained in:
Eugen Rochko 2016-02-17 23:16:08 +01:00
parent 75f1b1faf9
commit f127a1f30f
10 changed files with 304 additions and 57 deletions

View File

@ -1,7 +1,7 @@
PATH
remote: .
specs:
goldfinger (0.1.0)
goldfinger (0.1.1)
addressable (~> 2.4)
http (~> 1.0)
nokogiri (~> 1.6)

View File

@ -14,9 +14,16 @@ A Webfinger client for Ruby. Supports `application/xrd+xml` and `application/jrd
## Usage
data = Goldfinger.finger('acct:gargron@quitter.no')
data.link('http://schemas.google.com/g/2010#updates-from')[:href]
data.link('http://schemas.google.com/g/2010#updates-from').href
# => "https://quitter.no/api/statuses/user_timeline/7477.atom"
data.aliases
# => ["https://quitter.no/user/7477", "https://quitter.no/gargron"]
data.subject
# => "acct:gargron@quitter.no"
## RFC support
The gem only parses link data. It does not currently parse aliases, properties, or more complex structures.
The official Webfinger RFC is [7033](https://tools.ietf.org/html/rfc7033).

View File

@ -1,6 +1,6 @@
Gem::Specification.new do |s|
s.name = 'goldfinger'
s.version = '0.1.1'
s.version = '1.0.0'
s.platform = Gem::Platform::RUBY
s.required_ruby_version = '>= 2.0.0'
s.date = '2016-02-17'

View File

@ -1,4 +1,5 @@
require 'goldfinger/request'
require 'goldfinger/link'
require 'goldfinger/result'
require 'goldfinger/utils'
require 'goldfinger/client'
@ -13,6 +14,12 @@ module Goldfinger
class SSLError < Error
end
# Returns result for the Webfinger query
#
# @raise [Goldfinger::NotFoundError] Error raised when the Webfinger resource could not be retrieved
# @raise [Goldfinger::SSLError] Error raised when there was a SSL error when fetching the resource
# @param uri [String] A full resource identifier in the format acct:user@example.com
# @return [Goldfinger::Result]
def self.finger(uri)
Goldfinger::Client.new(uri).finger
end

View File

@ -39,7 +39,7 @@ module Goldfinger
def url_from_template(template)
xml = Nokogiri::XML(template)
links = xml.xpath('//xmlns:Link[@rel="lrdd"]', xmlns: 'http://docs.oasis-open.org/ns/xri/xrd-1.0')
links = xml.xpath('//xmlns:Link[@rel="lrdd"]')
raise Goldfinger::NotFoundError if links.empty?

53
lib/goldfinger/link.rb Normal file
View File

@ -0,0 +1,53 @@
module Goldfinger
# @!attribute [r] href
# @return [String] The href the link points to
# @!attribute [r] type
# @return [String] The mime type of the link
# @!attribute [r] rel
# @return [String] The relation descriptor of the link
class Link
attr_reader :href, :type, :rel
def initialize(a)
@href = a[:href]
@type = a[:type]
@rel = a[:rel]
@titles = a[:titles]
@properties = a[:properties]
end
# The "titles" object comprises zero or more name/value pairs whose
# names are a language tag or the string "und". The string is
# human-readable and describes the link relation.
# @see #title
# @return [Array] Array form of the hash
def titles
@titles.to_a
end
# The "properties" object within the link relation object comprises
# zero or more name/value pairs whose names are URIs (referred to as
# "property identifiers") and whose values are strings or nil.
# Properties are used to convey additional information about the link
# relation.
# @see #property
# @return [Array] Array form of the hash
def properties
@properties.to_a
end
# Returns a title for a language
# @param lang [String]
# @return [String]
def title(lang)
@titles[lang]
end
# Returns a property for a key
# @param key [String]
# @return [String]
def property(key)
@properties[key]
end
end
end

View File

@ -1,17 +1,57 @@
module Goldfinger
class Result
def initialize(headers, body)
@mime_type = headers.get(HTTP::Headers::CONTENT_TYPE).first
@body = body
@links = {}
@mime_type = headers.get(HTTP::Headers::CONTENT_TYPE).first
@body = body
@subject = nil
@aliases = []
@links = {}
@properties = {}
parse
end
# The value of the "subject" member is a URI that identifies the entity
# that the JRD describes.
# @return [String]
def subject
@subject
end
# The "aliases" array is an array of zero or more URI strings that
# identify the same entity as the "subject" URI.
# @return [Array]
def aliases
@aliases
end
# The "properties" object comprises zero or more name/value pairs whose
# names are URIs (referred to as "property identifiers") and whose
# values are strings or nil.
# @see #property
# @return [Array] Array form of the hash
def properties
@properties.to_a
end
# Returns a property for a key
# @param key [String]
# @return [String]
def property(key)
@properties[key]
end
# The "links" array has any number of member objects, each of which
# represents a link.
# @see #link
# @return [Array] Array form of the hash
def links
@links.to_a
end
# Returns a key for a relation
# @param key [String]
# @return [Goldfinger::Link]
def link(rel)
@links[rel]
end
@ -29,13 +69,38 @@ module Goldfinger
def parse_json
json = JSON.parse(@body)
json['links'].each { |link| @links[link['rel']] = Hash[link.keys.map { |key| [key.to_sym, link[key]] }] }
@subject = json['subject']
@aliases = json['aliases'] || []
@properties = json['properties'] || {}
json['links'].each do |link|
tmp = Hash[link.keys.map { |key| [key.to_sym, link[key]] }]
@links[link['rel']] = Goldfinger::Link.new(tmp)
end
end
def parse_xml
xml = Nokogiri::XML(@body)
links = xml.xpath('//xmlns:Link', xmlns: 'http://docs.oasis-open.org/ns/xri/xrd-1.0')
links.each { |link| @links[link.attribute('rel').value] = Hash[link.attributes.keys.map { |key| [key.to_sym, link.attribute(key).value] }] }
@subject = xml.at_xpath('//xmlns:Subject').content
@aliases = xml.xpath('//xmlns:Alias').map { |a| a.content }
properties = xml.xpath('/xmlns:XRD/xmlns:Property')
properties.each { |prop| @properties[prop.attribute('type').value] = prop.attribute('nil') ? nil : prop.content }
xml.xpath('//xmlns:Link').each do |link|
rel = link.attribute('rel').value
tmp = Hash[link.attributes.keys.map { |key| [key.to_sym, link.attribute(key).value] }]
tmp[:titles] = {}
tmp[:properties] = {}
link.xpath('.//xmlns:Title').each { |title| tmp[:titles][title.attribute('lang').value] = title.content }
link.xpath('.//xmlns:Property').each { |prop| tmp[:properties][prop.attribute('type').value] = prop.attribute('nil') ? nil : prop.content }
@links[rel] = Goldfinger::Link.new(tmp)
end
end
end
end

View File

@ -1 +1,77 @@
{"subject":"acct:gargron@quitter.no","aliases":["https:\/\/quitter.no\/user\/7477","https:\/\/quitter.no\/gargron"],"links":[{"rel":"http:\/\/webfinger.net\/rel\/profile-page","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/gmpg.org\/xfn\/11","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"describedby","type":"application\/rdf+xml","href":"https:\/\/quitter.no\/gargron\/foaf"},{"rel":"http:\/\/apinamespace.org\/atom","type":"application\/atomsvc+xml","href":"https:\/\/quitter.no\/api\/statusnet\/app\/service\/gargron.xml"},{"rel":"http:\/\/apinamespace.org\/twitter","href":"https:\/\/quitter.no\/api\/"},{"rel":"http:\/\/specs.openid.net\/auth\/2.0\/provider","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/schemas.google.com\/g\/2010#updates-from","type":"application\/atom+xml","href":"https:\/\/quitter.no\/api\/statuses\/user_timeline\/7477.atom"},{"rel":"magic-public-key","href":"data:application\/magic-public-key,RSA.1ZBkHTavLvxH3FzlKv4O6WtlILKRFfNami3_Rcu8EuogtXSYiS-bB6hElZfUCSHbC4uLemOA34PEhz__CDMozax1iI_t8dzjDnh1x0iFSup7pSfW9iXk_WU3Dm74yWWW2jildY41vWgrEstuQ1dJ8vVFfSJ9T_tO4c-T9y8vDI8=.AQAB"},{"rel":"salmon","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-replies","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-mention","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/ostatus.org\/schema\/1.0\/subscribe","template":"https:\/\/quitter.no\/main\/ostatussub?profile={uri}"}]}
{
"subject": "acct:gargron@quitter.no",
"aliases": [
"https://quitter.no/user/7477",
"https://quitter.no/gargron"
],
"properties": {
"http://webfinger.example/ns/name": "Bob Smith"
},
"links": [
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": "https://quitter.no/gargron"
},
{
"rel": "http://gmpg.org/xfn/11",
"type": "text/html",
"href": "https://quitter.no/gargron"
},
{
"rel": "describedby",
"type": "application/rdf+xml",
"href": "https://quitter.no/gargron/foaf"
},
{
"rel": "http://apinamespace.org/atom",
"type": "application/atomsvc+xml",
"href": "https://quitter.no/api/statusnet/app/service/gargron.xml"
},
{
"rel": "http://apinamespace.org/twitter",
"href": "https://quitter.no/api/"
},
{
"rel": "http://specs.openid.net/auth/2.0/provider",
"href": "https://quitter.no/gargron"
},
{
"rel": "http://schemas.google.com/g/2010#updates-from",
"type": "application/atom+xml",
"href": "https://quitter.no/api/statuses/user_timeline/7477.atom"
},
{
"rel": "magic-public-key",
"href": "data:application/magic-public-key,RSA.1ZBkHTavLvxH3FzlKv4O6WtlILKRFfNami3_Rcu8EuogtXSYiS-bB6hElZfUCSHbC4uLemOA34PEhz__CDMozax1iI_t8dzjDnh1x0iFSup7pSfW9iXk_WU3Dm74yWWW2jildY41vWgrEstuQ1dJ8vVFfSJ9T_tO4c-T9y8vDI8=.AQAB"
},
{
"rel": "salmon",
"href": "https://quitter.no/main/salmon/user/7477"
},
{
"rel": "http://salmon-protocol.org/ns/salmon-replies",
"href": "https://quitter.no/main/salmon/user/7477"
},
{
"rel": "http://salmon-protocol.org/ns/salmon-mention",
"href": "https://quitter.no/main/salmon/user/7477"
},
{
"rel": "http://ostatus.org/schema/1.0/subscribe",
"template": "https://quitter.no/main/ostatussub?profile={uri}"
},
{
"rel": "http://spec.example.net/photo/1.0",
"type": "image/jpeg",
"href": "http://photos.example.com/gpburdell.jpg",
"titles": {
"en": "User Photo",
"de": "Benutzerfoto"
},
"properties": {
"http://spec.example.net/created/1.0": "1970-01-01"
}
}
]
}

View File

@ -1,18 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
<Subject>acct:gargron@quitter.no</Subject>
<Alias>https://quitter.no/user/7477</Alias>
<Alias>https://quitter.no/gargron</Alias>
<Link rel="http://webfinger.net/rel/profile-page" type="text/html" href="https://quitter.no/gargron"/>
<Link rel="http://gmpg.org/xfn/11" type="text/html" href="https://quitter.no/gargron"/>
<Link rel="describedby" type="application/rdf+xml" href="https://quitter.no/gargron/foaf"/>
<Link rel="http://apinamespace.org/atom" type="application/atomsvc+xml" href="https://quitter.no/api/statusnet/app/service/gargron.xml"/>
<Link rel="http://apinamespace.org/twitter" href="https://quitter.no/api/"/>
<Link rel="http://specs.openid.net/auth/2.0/provider" href="https://quitter.no/gargron"/>
<Link rel="http://schemas.google.com/g/2010#updates-from" type="application/atom+xml" href="https://quitter.no/api/statuses/user_timeline/7477.atom"/>
<Link rel="magic-public-key" href="data:application/magic-public-key,RSA.1ZBkHTavLvxH3FzlKv4O6WtlILKRFfNami3_Rcu8EuogtXSYiS-bB6hElZfUCSHbC4uLemOA34PEhz__CDMozax1iI_t8dzjDnh1x0iFSup7pSfW9iXk_WU3Dm74yWWW2jildY41vWgrEstuQ1dJ8vVFfSJ9T_tO4c-T9y8vDI8=.AQAB"/>
<Link rel="salmon" href="https://quitter.no/main/salmon/user/7477"/>
<Link rel="http://salmon-protocol.org/ns/salmon-replies" href="https://quitter.no/main/salmon/user/7477"/>
<Link rel="http://salmon-protocol.org/ns/salmon-mention" href="https://quitter.no/main/salmon/user/7477"/>
<Link rel="http://ostatus.org/schema/1.0/subscribe" template="https://quitter.no/main/ostatussub?profile={uri}"/>
<Subject>acct:gargron@quitter.no</Subject>
<Alias>https://quitter.no/user/7477</Alias>
<Alias>https://quitter.no/gargron</Alias>
<Property type="http://webfinger.example/ns/name">Bob Smith</Property>
<Link rel="http://webfinger.net/rel/profile-page" type="text/html" href="https://quitter.no/gargron" />
<Link rel="http://gmpg.org/xfn/11" type="text/html" href="https://quitter.no/gargron" />
<Link rel="describedby" type="application/rdf+xml" href="https://quitter.no/gargron/foaf" />
<Link rel="http://apinamespace.org/atom" type="application/atomsvc+xml" href="https://quitter.no/api/statusnet/app/service/gargron.xml" />
<Link rel="http://apinamespace.org/twitter" href="https://quitter.no/api/" />
<Link rel="http://specs.openid.net/auth/2.0/provider" href="https://quitter.no/gargron" />
<Link rel="http://schemas.google.com/g/2010#updates-from" type="application/atom+xml" href="https://quitter.no/api/statuses/user_timeline/7477.atom" />
<Link rel="magic-public-key" href="data:application/magic-public-key,RSA.1ZBkHTavLvxH3FzlKv4O6WtlILKRFfNami3_Rcu8EuogtXSYiS-bB6hElZfUCSHbC4uLemOA34PEhz__CDMozax1iI_t8dzjDnh1x0iFSup7pSfW9iXk_WU3Dm74yWWW2jildY41vWgrEstuQ1dJ8vVFfSJ9T_tO4c-T9y8vDI8=.AQAB" />
<Link rel="salmon" href="https://quitter.no/main/salmon/user/7477" />
<Link rel="http://salmon-protocol.org/ns/salmon-replies" href="https://quitter.no/main/salmon/user/7477" />
<Link rel="http://salmon-protocol.org/ns/salmon-mention" href="https://quitter.no/main/salmon/user/7477" />
<Link rel="http://ostatus.org/schema/1.0/subscribe" template="https://quitter.no/main/ostatussub?profile={uri}" />
<Link rel="http://spec.example.net/photo/1.0" type="image/jpeg" href="http://photos.example.com/gpburdell.jpg">
<Title xml:lang="en">User Photo</Title>
<Title xml:lang="de">Benutzerfoto</Title>
<Property type="http://spec.example.net/created/1.0">1970-01-01</Property>
</Link>
</XRD>

View File

@ -1,41 +1,74 @@
describe Goldfinger::Result do
context 'application/xrd+xml' do
shared_examples 'a working finger result' do
subject { Goldfinger::Result.new(headers, body) }
describe '#links' do
it 'returns a non-empty array' do
expect(subject.links).to be_instance_of Array
expect(subject.links).to_not be_empty
end
end
describe '#link' do
it 'returns a value for a given rel' do
expect(subject.link('http://webfinger.net/rel/profile-page').href).to eql 'https://quitter.no/gargron'
end
it 'returns nil if no such link exists' do
expect(subject.link('zzzz')).to be_nil
end
it 'returns titles map' do
expect(subject.link('http://spec.example.net/photo/1.0').title('en')).to eql 'User Photo'
end
it 'returns a properties map' do
expect(subject.link('http://spec.example.net/photo/1.0').property('http://spec.example.net/created/1.0')).to eql '1970-01-01'
end
end
describe '#subject' do
it 'returns the subject' do
expect(subject.subject).to eql 'acct:gargron@quitter.no'
end
end
describe '#aliases' do
it 'returns a non-empty array' do
expect(subject.aliases).to be_instance_of Array
expect(subject.aliases).to_not be_empty
end
end
describe '#properties' do
it 'returns an array' do
expect(subject.properties).to be_instance_of Array
expect(subject.properties).to_not be_empty
end
end
describe '#property' do
it 'returns the value for a key' do
expect(subject.property('http://webfinger.example/ns/name')).to eql 'Bob Smith'
end
it 'returns nil if no such property exists' do
expect(subject.property('zzzz')).to be_nil
end
end
end
context 'when the input mime type is application/xrd+xml' do
let(:headers) { h = HTTP::Headers.new; h.set(HTTP::Headers::CONTENT_TYPE, 'application/xrd+xml'); h }
let(:body) { File.read(fixture_path('quitter.no_.well-known_webfinger.xml')) }
subject { Goldfinger::Result.new(headers, body) }
describe '#links' do
it 'returns a non-empty array' do
expect(subject.links).to be_instance_of Array
expect(subject.links).to_not be_empty
end
end
describe '#link' do
it 'returns a value for a given rel' do
expect(subject.link('http://webfinger.net/rel/profile-page')[:href]).to eql 'https://quitter.no/gargron'
end
end
it_behaves_like 'a working finger result'
end
context 'application/jrd+json' do
context 'when the input mime type is application/jrd+json' do
let(:headers) { h = HTTP::Headers.new; h.set(HTTP::Headers::CONTENT_TYPE, 'application/jrd+json'); h }
let(:body) { File.read(fixture_path('quitter.no_.well-known_webfinger.json')) }
subject { Goldfinger::Result.new(headers, body) }
describe '#links' do
it 'returns a non-empty array' do
expect(subject.links).to be_instance_of Array
expect(subject.links).to_not be_empty
end
end
describe '#link' do
it 'returns a value for a given rel' do
expect(subject.link('http://webfinger.net/rel/profile-page')[:href]).to eql 'https://quitter.no/gargron'
end
end
it_behaves_like 'a working finger result'
end
end