@ -11,97 +11,153 @@ class ResolveRemoteAccountService < BaseService
# @param [String] uri User URI in the form of username@domain
# @return [Account]
def call ( uri , update_profile = true , redirected = nil )
username , domain = uri . split ( '@' )
@username , @ domain = uri . split ( '@' )
return Account . find_local ( username ) if TagManager . instance . local_domain? ( domain )
return Account . find_local ( @ username) if TagManager . instance . local_domain? ( @ domain)
account = Account . find_remote ( username , domain )
return account unless account_needs_webfinger_update? ( account )
@account = Account . find_remote ( @username , @domain )
return @account unless webfinger_update_due?
Rails . logger . debug " Looking up webfinger for #{ uri } "
data = Goldfinger . finger ( " acct: #{ uri } " )
@webfinger = Goldfinger . finger ( " acct: #{ uri } " )
raise Goldfinger :: Error , 'Missing resource links' if data . link ( 'http://schemas.google.com/g/2010#updates-from' ) . nil? || data . link ( 'salmon' ) . nil? || data . link ( 'http://webfinger.net/rel/profile-page' ) . nil? || data . link ( 'magic-public-key' ) . nil ?
raise Goldfinger :: Error , 'Missing resource links' if links_missing ?
# Disallow account hijacking
confirmed_username , confirmed_domain = data . subject . gsub ( / \ Aacct: / , '' ) . split ( '@' )
confirmed_username , confirmed_domain = @webfinger . subject . gsub ( / \ Aacct: / , '' ) . split ( '@' )
unless confirmed_username . casecmp ( username ) . zero? && confirmed_domain . casecmp ( domain ) . zero?
if confirmed_username . casecmp ( @username ) . zero? && confirmed_domain . casecmp ( @domain ) . zero?
@username = confirmed_username
@domain = confirmed_domain
else
return call ( " #{ confirmed_username } @ #{ confirmed_domain } " , update_profile , true ) if redirected . nil?
raise Goldfinger :: Error , 'Requested and returned acct URI do not match'
raise Goldfinger :: Error , 'Requested and returned acct URIs do not match'
end
return Account . find_local ( confirmed_ username) if TagManager . instance . local_domain? ( confirmed_ domain)
return Account . find_local ( @ username) if TagManager . instance . local_domain? ( @ domain)
confirmed_account = Account . find_remote ( confirmed_username , confirmed_domain )
if confirmed_account . nil ?
Rails . logger . debug " Creating new remote account for #{ uri } "
RedisLock . acquire ( lock_options ) do | lock |
if lock . acquired ?
@account = Account . find_remote ( @username , @domain )
domain_block = DomainBlock . find_by ( domain : domain )
account = Account . new ( username : confirmed_username , domain : confirmed_domain )
account . suspended = true if domain_block && domain_block . suspend?
account . silenced = true if domain_block && domain_block . silence?
account . private_key = nil
else
account = confirmed_account
create_account if @account . nil?
update_account
update_account_profile if update_profile
end
end
account . last_webfingered_at = Time . now . utc
@account
end
account . remote_url = data . link ( 'http://schemas.google.com/g/2010#updates-from' ) . href
account . salmon_url = data . link ( 'salmon' ) . href
account . url = data . link ( 'http://webfinger.net/rel/profile-page' ) . href
account . public_key = magic_key_to_pem ( data . link ( 'magic-public-key' ) . href )
private
body , xml = get_feed ( account . remote_url )
hubs = get_hubs ( xml )
def links_missing?
@webfinger . link ( 'http://schemas.google.com/g/2010#updates-from' ) . nil? ||
@webfinger . link ( 'salmon' ) . nil? ||
@webfinger . link ( 'http://webfinger.net/rel/profile-page' ) . nil? ||
@webfinger . link ( 'magic-public-key' ) . nil?
end
account . uri = get_account_uri ( xml )
account . hub_url = hubs . first . attribute ( 'href' ) . value
def webfinger_update_due?
@account . nil? || @account . last_webfingered_at . nil? || @account . last_webfingered_at < = 1 . day . ago
end
begin
account . save!
get_profile ( body , account ) if update_profile
rescue ActiveRecord :: RecordNotUnique
# The account has been added by another worker!
return Account . find_remote ( confirmed_username , confirmed_domain )
def create_account
Rails . logger . debug " Creating new remote account for #{ @username } @ #{ @domain } "
@account = Account . new ( username : @username , domain : @domain )
@account . suspended = true if auto_suspend?
@account . silenced = true if auto_silence?
@account . private_key = nil
end
account
def update_account
@account . last_webfingered_at = Time . now . utc
@account . remote_url = atom_url
@account . salmon_url = salmon_url
@account . url = url
@account . public_key = public_key
@account . uri = canonical_uri
@account . hub_url = hub_url
@account . save!
end
private
def auto_suspend?
domain_block && domain_block . suspend?
end
def auto_silence?
domain_block && domain_block . silence?
end
def domain_block
return @domain_block if defined? ( @domain_block )
@domain_block = DomainBlock . find_by ( domain : @domain )
end
def atom_url
@atom_url || = @webfinger . link ( 'http://schemas.google.com/g/2010#updates-from' ) . href
end
def account_needs_webfinger_update? ( account )
account & . last_webfingered_at . nil? || account . last_webfingered_at < = 1 . day . ago
def salmon_url
@salmon_url || = @webfinger . link ( 'salmon' ) . href
end
def get_feed ( url )
response = Request . new ( :get , url ) . perform
raise Goldfinger :: Error , " Feed attempt failed for #{ url } : HTTP #{ response . code } " unless response . code == 200
[ response . to_s , Nokogiri :: XML ( response ) ]
def url
@url || = @webfinger . link ( 'http://webfinger.net/rel/profile-page' ) . href
end
def get_hubs ( xml )
hubs = xml . xpath ( '//xmlns:link[@rel="hub"]' )
raise Goldfinger :: Error , 'No PubSubHubbub hubs found' if hubs . empty? || hubs . first . attribute ( 'href' ) . nil?
hubs
def public_key
@public_key || = magic_key_to_pem ( @webfinger . link ( 'magic-public-key' ) . href )
end
def get_account_uri ( xml )
author_uri = xml . at_xpath ( '/xmlns:feed/xmlns:author/xmlns:uri' )
def canonical_uri
return @canonical_uri if defined? ( @canonical_uri )
author_uri = atom . at_xpath ( '/xmlns:feed/xmlns:author/xmlns:uri' )
if author_uri . nil?
owner = xml . at_xpath ( '/xmlns:feed' ) . at_xpath ( './dfrn:owner' , dfrn : DFRN_NS )
owner = atom . at_xpath ( '/xmlns:feed' ) . at_xpath ( './dfrn:owner' , dfrn : DFRN_NS )
author_uri = owner . at_xpath ( './xmlns:uri' ) unless owner . nil?
end
raise Goldfinger :: Error , 'Author URI could not be found' if author_uri . nil?
author_uri . content
@canonical_uri = author_uri . content
end
def hub_url
return @hub_url if defined? ( @hub_url )
hubs = atom . xpath ( '//xmlns:link[@rel="hub"]' )
raise Goldfinger :: Error , 'No PubSubHubbub hubs found' if hubs . empty? || hubs . first [ 'href' ] . nil?
@hub_url = hubs . first [ 'href' ]
end
def atom_body
return @atom_body if defined? ( @atom_body )
response = Request . new ( :get , atom_url ) . perform
raise Goldfinger :: Error , " Feed attempt failed for #{ atom_url } : HTTP #{ response . code } " unless response . code == 200
@atom_body = response . to_s
end
def atom
return @atom if defined? ( @atom )
@atom = Nokogiri :: XML ( atom_body )
end
def update_account_profile
RemoteProfileUpdateWorker . perform_async ( @account . id , atom_body . force_encoding ( 'UTF-8' ) , false )
end
def get_profile ( body , account )
RemoteProfileUpdateWorker . perform_async ( account . id , body . force_encoding ( 'UTF-8' ) , false )
def lock_options
{ redis : Redis . current , key : " resolve: #{ @username } @ #{ @domain } " }
end
end