activitypub-academy/app/lib/webfinger.rb
Claire 1145dbd327
Improve error reporting and logging when processing remote accounts (#15605)
* Add a more descriptive PrivateNetworkAddressError exception class

* Remove unnecessary exception class to rescue clause

* Remove unnecessary include to JsonLdHelper

* Give more neutral error message when too many webfinger redirects

* Remove unnecessary guard condition

* Rework how “ActivityPub::FetchRemoteAccountService” handles errors

Add “suppress_errors” keyword argument to avoid raising errors in
ActivityPub::FetchRemoteAccountService#call (default/previous behavior).

* Rework how “ActivityPub::FetchRemoteKeyService” handles errors

Add “suppress_errors” keyword argument to avoid raising errors in
ActivityPub::FetchRemoteKeyService#call (default/previous behavior).

* Fix Webfinger::RedirectError not being a subclass of Webfinger::Error

* Add suppress_errors option to ResolveAccountService

Defaults to true (to preserve previous behavior). If set to false,
errors will be raised instead of caught, allowing the caller to be
informed of what went wrong.

* Return more precise error when failing to fetch account signing AP payloads

* Add tests

* Fixes

* Refactor error handling a bit

* Fix various issues

* Add specific error when provided Digest is not 256 bits of base64-encoded data

* Please CodeClimate

* Improve webfinger error reporting
2022-09-20 23:30:26 +02:00

116 lines
2.9 KiB
Ruby

# frozen_string_literal: true
class Webfinger
class Error < StandardError; end
class GoneError < Error; end
class RedirectError < Error; end
class Response
attr_reader :uri
def initialize(uri, body)
@uri = uri
@json = Oj.load(body, mode: :strict)
validate_response!
end
def subject
@json['subject']
end
def link(rel, attribute)
links.dig(rel, attribute)
end
private
def links
@links ||= @json['links'].index_by { |link| link['rel'] }
end
def validate_response!
raise Webfinger::Error, "Missing subject in response for #{@uri}" if subject.blank?
end
end
def initialize(uri)
_, @domain = uri.split('@')
raise ArgumentError, 'Webfinger requested for local account' if @domain.nil?
@uri = uri
end
def perform
Response.new(@uri, body_from_webfinger)
rescue Oj::ParseError
raise Webfinger::Error, "Invalid JSON in response for #{@uri}"
rescue Addressable::URI::InvalidURIError
raise Webfinger::Error, "Invalid URI for #{@uri}"
end
private
def body_from_webfinger(url = standard_url, use_fallback = true)
webfinger_request(url).perform do |res|
if res.code == 200
body = res.body_with_limit
raise Webfinger::Error, "Request for #{@uri} returned empty response" if body.empty?
body
elsif res.code == 404 && use_fallback
body_from_host_meta
elsif res.code == 410
raise Webfinger::GoneError, "#{@uri} is gone from the server"
else
raise Webfinger::Error, "Request for #{@uri} returned HTTP #{res.code}"
end
end
end
def body_from_host_meta
host_meta_request.perform do |res|
if res.code == 200
body_from_webfinger(url_from_template(res.body_with_limit), false)
else
raise Webfinger::Error, "Request for #{@uri} returned HTTP #{res.code}"
end
end
end
def url_from_template(str)
link = Nokogiri::XML(str).at_xpath('//xmlns:Link[@rel="lrdd"]')
if link.present?
link['template'].gsub('{uri}', @uri)
else
raise Webfinger::Error, "Request for #{@uri} returned host-meta without link to Webfinger"
end
rescue Nokogiri::XML::XPath::SyntaxError
raise Webfinger::Error, "Invalid XML encountered in host-meta for #{@uri}"
end
def host_meta_request
Request.new(:get, host_meta_url).add_headers('Accept' => 'application/xrd+xml, application/xml, text/xml')
end
def webfinger_request(url)
Request.new(:get, url).add_headers('Accept' => 'application/jrd+json, application/json')
end
def standard_url
if @domain.end_with? ".onion"
"http://#{@domain}/.well-known/webfinger?resource=#{@uri}"
else
"https://#{@domain}/.well-known/webfinger?resource=#{@uri}"
end
end
def host_meta_url
if @domain.end_with? ".onion"
"http://#{@domain}/.well-known/host-meta"
else
"https://#{@domain}/.well-known/host-meta"
end
end
end