add webfinger generator/parser from raven24's gem
This commit is contained in:
parent
46295b9c0c
commit
c950e7a94b
4 changed files with 328 additions and 4 deletions
11
.rubocop.yml
11
.rubocop.yml
|
|
@ -11,13 +11,16 @@ Metrics/LineLength:
|
||||||
|
|
||||||
# Too short methods lead to extraction of single-use methods, which can make
|
# Too short methods lead to extraction of single-use methods, which can make
|
||||||
# the code easier to read (by naming things), but can also clutter the class
|
# the code easier to read (by naming things), but can also clutter the class
|
||||||
Metrics/MethodLength:
|
Metrics/MethodLength:
|
||||||
Max: 20
|
Max: 20
|
||||||
|
|
||||||
# The guiding principle of classes is SRP, SRP can't be accurately measured by LoC
|
# The guiding principle of classes is SRP, SRP can't be accurately measured by LoC
|
||||||
Metrics/ClassLength:
|
Metrics/ClassLength:
|
||||||
Max: 1500
|
Max: 1500
|
||||||
|
|
||||||
|
Metrics/ModuleLength:
|
||||||
|
Max: 1500
|
||||||
|
|
||||||
# No space makes the method definition shorter and differentiates
|
# No space makes the method definition shorter and differentiates
|
||||||
# from a regular assignment.
|
# from a regular assignment.
|
||||||
Style/SpaceAroundEqualsInParameterDefault:
|
Style/SpaceAroundEqualsInParameterDefault:
|
||||||
|
|
@ -66,7 +69,7 @@ Lint/AssignmentInCondition:
|
||||||
AllowSafeAssignment: false
|
AllowSafeAssignment: false
|
||||||
|
|
||||||
# A specialized exception class will take one or more arguments and construct the message from it.
|
# A specialized exception class will take one or more arguments and construct the message from it.
|
||||||
# So both variants make sense.
|
# So both variants make sense.
|
||||||
Style/RaiseArgs:
|
Style/RaiseArgs:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
|
@ -124,11 +127,11 @@ Lint/ShadowingOuterLocalVariable:
|
||||||
|
|
||||||
# Check with yard instead.
|
# Check with yard instead.
|
||||||
Style/Documentation:
|
Style/Documentation:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
# This is just silly. Calling the argument `other` in all cases makes no sense.
|
# This is just silly. Calling the argument `other` in all cases makes no sense.
|
||||||
Style/OpMethod:
|
Style/OpMethod:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
# There are valid cases, for example debugging Cucumber steps,
|
# There are valid cases, for example debugging Cucumber steps,
|
||||||
# also they'll fail CI anyway
|
# also they'll fail CI anyway
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,4 @@ end
|
||||||
require "diaspora_federation/web_finger/exceptions"
|
require "diaspora_federation/web_finger/exceptions"
|
||||||
require "diaspora_federation/web_finger/xrd_document"
|
require "diaspora_federation/web_finger/xrd_document"
|
||||||
require "diaspora_federation/web_finger/host_meta"
|
require "diaspora_federation/web_finger/host_meta"
|
||||||
|
require "diaspora_federation/web_finger/web_finger"
|
||||||
|
|
|
||||||
203
lib/diaspora_federation/web_finger/web_finger.rb
Normal file
203
lib/diaspora_federation/web_finger/web_finger.rb
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
module DiasporaFederation
|
||||||
|
module WebFinger
|
||||||
|
##
|
||||||
|
# The WebFinger document used for Diaspora* user discovery is based on an older
|
||||||
|
# draft of the specification you can find in the wiki of the "webfinger" project
|
||||||
|
# on {http://code.google.com/p/webfinger/wiki/WebFingerProtocol Google Code}
|
||||||
|
# (from around 2010).
|
||||||
|
#
|
||||||
|
# In the meantime an actual RFC draft has been in development, which should
|
||||||
|
# serve as a base for all future changes of this implementation.
|
||||||
|
#
|
||||||
|
# @example Creating a WebFinger document from account data
|
||||||
|
# wf = WebFinger.from_account({
|
||||||
|
# acct_uri: "acct:user@server.example",
|
||||||
|
# alias_url: "https://server.example/people/0123456789abcdef",
|
||||||
|
# hcard_url: "https://server.example/hcard/users/user",
|
||||||
|
# seed_url: "https://server.example/",
|
||||||
|
# profile_url: "https://server.example/u/user",
|
||||||
|
# updates_url: "https://server.example/public/user.atom",
|
||||||
|
# guid: "0123456789abcdef",
|
||||||
|
# pubkey: "ABCDEF=="
|
||||||
|
# })
|
||||||
|
# xml_string = wf.to_xml
|
||||||
|
#
|
||||||
|
# @example Creating a WebFinger instance from an xml document
|
||||||
|
# wf = WebFinger.from_xml(xml_string)
|
||||||
|
# ...
|
||||||
|
# hcard_url = wf.hcard_url
|
||||||
|
# ...
|
||||||
|
#
|
||||||
|
# @see http://tools.ietf.org/html/draft-jones-appsawg-webfinger "WebFinger" -
|
||||||
|
# current draft
|
||||||
|
# @see http://code.google.com/p/webfinger/wiki/CommonLinkRelations
|
||||||
|
# @see http://www.iana.org/assignments/link-relations/link-relations.xhtml
|
||||||
|
# official list of IANA link relations
|
||||||
|
class WebFinger
|
||||||
|
private_class_method :new
|
||||||
|
|
||||||
|
attr_reader :acct_uri, :alias_url, :hcard_url, :seed_url, :profile_url, :updates_url
|
||||||
|
|
||||||
|
# @deprecated Either convert these to +Property+ elements or move to the
|
||||||
|
# +hCard+, which actually has fields for an +UID+ and +KEY+ defined in
|
||||||
|
# the +vCard+ specification (will affect older Diaspora* installations).
|
||||||
|
attr_reader :guid, :pubkey
|
||||||
|
|
||||||
|
# +hcard+ link relation
|
||||||
|
REL_HCARD = "http://microformats.org/profile/hcard"
|
||||||
|
|
||||||
|
# +seed_location+ link relation
|
||||||
|
REL_SEED = "http://joindiaspora.com/seed_location"
|
||||||
|
|
||||||
|
# @deprecated This should be a +Property+ or moved to the +hCard+, but +Link+
|
||||||
|
# is inappropriate according to the specification (will affect older
|
||||||
|
# Diaspora* installations).
|
||||||
|
# +guid+ link relation
|
||||||
|
REL_GUID = "http://joindiaspora.com/guid"
|
||||||
|
|
||||||
|
# +profile-page+ link relation.
|
||||||
|
# @note This might just as well be an +Alias+ instead of a +Link+.
|
||||||
|
REL_PROFILE = "http://webfinger.net/rel/profile-page"
|
||||||
|
|
||||||
|
# Atom feed link relation
|
||||||
|
REL_UPDATES = "http://schemas.google.com/g/2010#updates-from"
|
||||||
|
|
||||||
|
# @deprecated This should be a +Property+ or moved to the +hcard+, but +Link+
|
||||||
|
# is inappropriate according to the specification (will affect older
|
||||||
|
# Diaspora* installations).
|
||||||
|
# +diaspora-public-key+ link relation
|
||||||
|
REL_PUBKEY = "diaspora-public-key"
|
||||||
|
|
||||||
|
# Create the XML string from the current WebFinger instance
|
||||||
|
# @return [String] XML string
|
||||||
|
def to_xml
|
||||||
|
doc = XrdDocument.new
|
||||||
|
doc.subject = @acct_uri
|
||||||
|
doc.aliases << @alias_url
|
||||||
|
|
||||||
|
add_links_to(doc)
|
||||||
|
|
||||||
|
doc.to_xml
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create a WebFinger instance from the given account data Hash.
|
||||||
|
# @param [Hash] data account data
|
||||||
|
# @return [WebFinger] WebFinger instance
|
||||||
|
# @raise [InvalidData] if the given data Hash is invalid or incomplete
|
||||||
|
def self.from_account(data)
|
||||||
|
raise InvalidData unless account_data_complete?(data)
|
||||||
|
|
||||||
|
wf = allocate
|
||||||
|
wf.instance_eval {
|
||||||
|
@acct_uri = data[:acct_uri]
|
||||||
|
@alias_url = data[:alias_url]
|
||||||
|
@hcard_url = data[:hcard_url]
|
||||||
|
@seed_url = data[:seed_url]
|
||||||
|
@profile_url = data[:profile_url]
|
||||||
|
@updates_url = data[:updates_url]
|
||||||
|
|
||||||
|
# TODO: change me! #########
|
||||||
|
@guid = data[:guid]
|
||||||
|
@pubkey = data[:pubkey]
|
||||||
|
#############################
|
||||||
|
}
|
||||||
|
wf
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create a WebFinger instance from the given XML string.
|
||||||
|
# @param [String] webfinger_xml WebFinger XML string
|
||||||
|
# @return [WebFinger] WebFinger instance
|
||||||
|
def self.from_xml(webfinger_xml)
|
||||||
|
data = XrdDocument.xml_data(webfinger_xml)
|
||||||
|
raise InvalidData unless xml_data_valid?(data)
|
||||||
|
|
||||||
|
hcard, seed, guid, profile, updates, pubkey = parse_links(data)
|
||||||
|
|
||||||
|
wf = allocate
|
||||||
|
wf.instance_eval {
|
||||||
|
@acct_uri = data[:subject]
|
||||||
|
@alias_url = data[:aliases].first
|
||||||
|
@hcard_url = hcard[:href]
|
||||||
|
@seed_url = seed[:href]
|
||||||
|
@profile_url = profile[:href]
|
||||||
|
@updates_url = updates[:href]
|
||||||
|
|
||||||
|
# TODO: change me! ##########
|
||||||
|
@guid = guid[:href]
|
||||||
|
@pubkey = pubkey[:href]
|
||||||
|
##############################
|
||||||
|
}
|
||||||
|
wf
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Checks the given account data Hash for correct type and completeness.
|
||||||
|
# @param [Hash] data account data
|
||||||
|
# @return [Boolean] validation result
|
||||||
|
def self.account_data_complete?(data)
|
||||||
|
!data.nil? && data.instance_of?(Hash) &&
|
||||||
|
data.key?(:acct_uri) && data.key?(:alias_url) &&
|
||||||
|
data.key?(:hcard_url) && data.key?(:seed_url) &&
|
||||||
|
data.key?(:guid) && data.key?(:profile_url) &&
|
||||||
|
data.key?(:updates_url) && data.key?(:pubkey)
|
||||||
|
end
|
||||||
|
private_class_method :account_data_complete?
|
||||||
|
|
||||||
|
# Does some rudimentary checking on the data Hash produced from parsing the
|
||||||
|
# XML string
|
||||||
|
# @param [Hash] data XML data
|
||||||
|
# @return [Boolean] validation result
|
||||||
|
def self.xml_data_valid?(data)
|
||||||
|
data.key?(:subject) && data.key?(:aliases) && data.key?(:links)
|
||||||
|
end
|
||||||
|
private_class_method :xml_data_valid?
|
||||||
|
|
||||||
|
def add_links_to(doc)
|
||||||
|
doc.links << {rel: REL_HCARD,
|
||||||
|
type: "text/html",
|
||||||
|
href: @hcard_url}
|
||||||
|
doc.links << {rel: REL_SEED,
|
||||||
|
type: "text/html",
|
||||||
|
href: @seed_url}
|
||||||
|
|
||||||
|
# TODO: change me! ##############
|
||||||
|
doc.links << {rel: REL_GUID,
|
||||||
|
type: "text/html",
|
||||||
|
href: @guid}
|
||||||
|
##################################
|
||||||
|
|
||||||
|
doc.links << {rel: REL_PROFILE,
|
||||||
|
type: "text/html",
|
||||||
|
href: @profile_url}
|
||||||
|
doc.links << {rel: REL_UPDATES,
|
||||||
|
type: "application/atom+xml",
|
||||||
|
href: @updates_url}
|
||||||
|
|
||||||
|
# TODO: change me! ##############
|
||||||
|
doc.links << {rel: REL_PUBKEY,
|
||||||
|
type: "RSA",
|
||||||
|
href: @pubkey}
|
||||||
|
##################################
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.parse_links(data)
|
||||||
|
links = data[:links]
|
||||||
|
hcard = parse_link(links, REL_HCARD)
|
||||||
|
seed = parse_link(links, REL_SEED)
|
||||||
|
guid = parse_link(links, REL_GUID)
|
||||||
|
profile = parse_link(links, REL_PROFILE)
|
||||||
|
updates = parse_link(links, REL_UPDATES)
|
||||||
|
pubkey = parse_link(links, REL_PUBKEY)
|
||||||
|
raise InvalidData unless [hcard, seed, guid, profile, updates, pubkey].all?
|
||||||
|
[hcard, seed, guid, profile, updates, pubkey]
|
||||||
|
end
|
||||||
|
private_class_method :parse_links
|
||||||
|
|
||||||
|
def self.parse_link(links, rel)
|
||||||
|
links.find {|l| l[:rel] == rel }
|
||||||
|
end
|
||||||
|
private_class_method :parse_link
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
117
spec/lib/web_finger/web_finger_spec.rb
Normal file
117
spec/lib/web_finger/web_finger_spec.rb
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
module DiasporaFederation
|
||||||
|
describe WebFinger::WebFinger do
|
||||||
|
acct = "acct:user@pod.example.tld"
|
||||||
|
alias_url = "http://pod.example.tld/"
|
||||||
|
hcard_url = "https://pod.example.tld/hcard/users/abcdef0123456789"
|
||||||
|
seed_url = "https://pod.geraspora.de/"
|
||||||
|
guid = "abcdef0123456789"
|
||||||
|
profile_url = "https://pod.example.tld/u/user"
|
||||||
|
updates_url = "https://pod.example.tld/public/user.atom"
|
||||||
|
pubkey = "AAAAAA=="
|
||||||
|
|
||||||
|
xml = <<-XML
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
|
||||||
|
<Subject>#{acct}</Subject>
|
||||||
|
<Alias>#{alias_url}</Alias>
|
||||||
|
<Link rel="http://microformats.org/profile/hcard" type="text/html" href="#{hcard_url}"/>
|
||||||
|
<Link rel="http://joindiaspora.com/seed_location" type="text/html" href="#{seed_url}"/>
|
||||||
|
<Link rel="http://joindiaspora.com/guid" type="text/html" href="#{guid}"/>
|
||||||
|
<Link rel="http://webfinger.net/rel/profile-page" type="text/html" href="#{profile_url}"/>
|
||||||
|
<Link rel="http://schemas.google.com/g/2010#updates-from" type="application/atom+xml" href="#{updates_url}"/>
|
||||||
|
<Link rel="diaspora-public-key" type="RSA" href="#{pubkey}"/>
|
||||||
|
</XRD>
|
||||||
|
XML
|
||||||
|
|
||||||
|
it "must not create blank instances" do
|
||||||
|
expect { WebFinger::WebFinger.new }.to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
context "generation" do
|
||||||
|
it "creates a nice XML document" do
|
||||||
|
wf = WebFinger::WebFinger.from_account(
|
||||||
|
acct_uri: acct,
|
||||||
|
alias_url: alias_url,
|
||||||
|
hcard_url: hcard_url,
|
||||||
|
seed_url: seed_url,
|
||||||
|
profile_url: profile_url,
|
||||||
|
updates_url: updates_url,
|
||||||
|
guid: guid,
|
||||||
|
pubkey: pubkey
|
||||||
|
)
|
||||||
|
expect(wf.to_xml).to eq(xml)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails if some params are missing" do
|
||||||
|
expect {
|
||||||
|
WebFinger::WebFinger.from_account(
|
||||||
|
acct_uri: acct,
|
||||||
|
alias_url: alias_url,
|
||||||
|
hcard_url: hcard_url
|
||||||
|
)
|
||||||
|
}.to raise_error(WebFinger::InvalidData)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails if nothing was given" do
|
||||||
|
expect { WebFinger::WebFinger.from_account({}) }.to raise_error(WebFinger::InvalidData)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "parsing" do
|
||||||
|
it "reads its own output" do
|
||||||
|
wf = WebFinger::WebFinger.from_xml(xml)
|
||||||
|
expect(wf.acct_uri).to eq(acct)
|
||||||
|
expect(wf.alias_url).to eq(alias_url)
|
||||||
|
expect(wf.hcard_url).to eq(hcard_url)
|
||||||
|
expect(wf.seed_url).to eq(seed_url)
|
||||||
|
expect(wf.profile_url).to eq(profile_url)
|
||||||
|
expect(wf.updates_url).to eq(updates_url)
|
||||||
|
|
||||||
|
expect(wf.guid).to eq(guid)
|
||||||
|
expect(wf.pubkey).to eq(pubkey)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "reads old-style XML" do
|
||||||
|
historic_xml = <<-XML
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
|
||||||
|
<Subject>#{acct}</Subject>
|
||||||
|
<Alias>#{alias_url}</Alias>
|
||||||
|
<Link rel="http://microformats.org/profile/hcard" type="text/html" href="#{hcard_url}"/>
|
||||||
|
<Link rel="http://joindiaspora.com/seed_location" type = "text/html" href="#{seed_url}"/>
|
||||||
|
<Link rel="http://joindiaspora.com/guid" type = "text/html" href="#{guid}"/>
|
||||||
|
|
||||||
|
<Link rel="http://webfinger.net/rel/profile-page" type="text/html" href="#{profile_url}"/>
|
||||||
|
<Link rel="http://schemas.google.com/g/2010#updates-from" type="application/atom+xml" href="#{updates_url}"/>
|
||||||
|
|
||||||
|
<Link rel="diaspora-public-key" type = "RSA" href="#{pubkey}"/>
|
||||||
|
</XRD>
|
||||||
|
XML
|
||||||
|
|
||||||
|
wf = WebFinger::WebFinger.from_xml(historic_xml)
|
||||||
|
expect(wf.acct_uri).to eq(acct)
|
||||||
|
expect(wf.alias_url).to eq(alias_url)
|
||||||
|
expect(wf.hcard_url).to eq(hcard_url)
|
||||||
|
expect(wf.seed_url).to eq(seed_url)
|
||||||
|
expect(wf.profile_url).to eq(profile_url)
|
||||||
|
expect(wf.updates_url).to eq(updates_url)
|
||||||
|
|
||||||
|
expect(wf.guid).to eq(guid)
|
||||||
|
expect(wf.pubkey).to eq(pubkey)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails if the document is empty" do
|
||||||
|
invalid_xml = <<XML
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
|
||||||
|
</XRD>
|
||||||
|
XML
|
||||||
|
expect { WebFinger::WebFinger.from_xml(invalid_xml) }.to raise_error(WebFinger::InvalidData)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails if the document is not XML" do
|
||||||
|
expect { WebFinger::WebFinger.from_xml("") }.to raise_error(WebFinger::InvalidDocument)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue