handle existing guids on receive

This commit is contained in:
Benjamin Neff 2016-05-07 18:59:40 +02:00
parent f3466bcfd6
commit d55be67df1
3 changed files with 111 additions and 47 deletions

View file

@ -20,15 +20,13 @@ module Workers
DiasporaFederation::Salmon::InvalidAlgorithm, DiasporaFederation::Salmon::InvalidAlgorithm,
DiasporaFederation::Salmon::InvalidEncoding, DiasporaFederation::Salmon::InvalidEncoding,
Diaspora::Federation::AuthorIgnored, Diaspora::Federation::AuthorIgnored,
Diaspora::Federation::InvalidAuthor,
# TODO: deprecated # TODO: deprecated
DiasporaFederation::Salmon::MissingMagicEnvelope, DiasporaFederation::Salmon::MissingMagicEnvelope,
DiasporaFederation::Salmon::MissingAuthor, DiasporaFederation::Salmon::MissingAuthor,
DiasporaFederation::Salmon::MissingHeader, DiasporaFederation::Salmon::MissingHeader,
DiasporaFederation::Salmon::InvalidHeader => e DiasporaFederation::Salmon::InvalidHeader => e
logger.warn "don't retry for error: #{e.class}" logger.warn "don't retry for error: #{e.class}"
rescue ActiveRecord::RecordInvalid => e
logger.warn "failed to save received object: #{e.record.errors.full_messages}"
raise e unless e.message.include? "already been taken"
end end
end end
end end

View file

@ -8,6 +8,10 @@ module Diaspora
# Raised, if author is ignored by the relayable parent author # Raised, if author is ignored by the relayable parent author
class AuthorIgnored < RuntimeError class AuthorIgnored < RuntimeError
end end
# Raised, if the author of the existing object doesn't match the received author
class InvalidAuthor < RuntimeError
end
end end
end end

View file

@ -1,6 +1,8 @@
module Diaspora module Diaspora
module Federation module Federation
module Receive module Receive
extend Diaspora::Logging
def self.account_deletion(entity) def self.account_deletion(entity)
AccountDeletion.new( AccountDeletion.new(
person: author_of(entity), person: author_of(entity),
@ -9,14 +11,17 @@ module Diaspora
end end
def self.comment(entity) def self.comment(entity)
author = author_of(entity)
ignore_existing_guid(Comment, entity.guid, author) do
Comment.new( Comment.new(
author: author_of(entity), author: author,
guid: entity.guid, guid: entity.guid,
created_at: entity.created_at, created_at: entity.created_at,
text: entity.text, text: entity.text,
commentable: Post.find_by(guid: entity.parent_guid) commentable: Post.find_by(guid: entity.parent_guid)
).tap {|comment| save_relayable(comment, entity) } ).tap {|comment| save_relayable(comment, entity) }
end end
end
def self.contact(entity) def self.contact(entity)
recipient = Person.find_by(diaspora_handle: entity.recipient).owner recipient = Person.find_by(diaspora_handle: entity.recipient).owner
@ -31,8 +36,10 @@ module Diaspora
end end
def self.conversation(entity) def self.conversation(entity)
author = author_of(entity)
try_load_existing_guid(Conversation, entity.guid, author) do
Conversation.new( Conversation.new(
author: author_of(entity), author: author,
guid: entity.guid, guid: entity.guid,
subject: entity.subject, subject: entity.subject,
created_at: entity.created_at, created_at: entity.created_at,
@ -42,19 +49,25 @@ module Diaspora
conversation.save! conversation.save!
end end
end end
end
def self.like(entity) def self.like(entity)
author = author_of(entity)
ignore_existing_guid(Like, entity.guid, author) do
Like.new( Like.new(
author: author_of(entity), author: author,
guid: entity.guid, guid: entity.guid,
positive: entity.positive, positive: entity.positive,
target: entity.parent_type.constantize.find_by(guid: entity.parent_guid) target: entity.parent_type.constantize.find_by(guid: entity.parent_guid)
).tap {|like| save_relayable(like, entity) } ).tap {|like| save_relayable(like, entity) }
end end
end
def self.message(entity) def self.message(entity)
ignore_existing_guid(Message, entity.guid, author_of(entity)) do
build_message(entity).tap(&:save!) build_message(entity).tap(&:save!)
end end
end
def self.participation(entity) def self.participation(entity)
parent = entity.parent_type.constantize.find_by(guid: entity.parent_guid) parent = entity.parent_type.constantize.find_by(guid: entity.parent_guid)
@ -73,8 +86,10 @@ module Diaspora
end end
def self.poll_participation(entity) def self.poll_participation(entity)
author = author_of(entity)
ignore_existing_guid(PollParticipation, entity.guid, author) do
PollParticipation.new( PollParticipation.new(
author: author_of(entity), author: author,
guid: entity.guid, guid: entity.guid,
poll: Poll.find_by(guid: entity.parent_guid) poll: Poll.find_by(guid: entity.parent_guid)
).tap do |poll_participation| ).tap do |poll_participation|
@ -83,6 +98,7 @@ module Diaspora
save_relayable(poll_participation, entity) save_relayable(poll_participation, entity)
end end
end end
end
def self.profile(entity) def self.profile(entity)
author_of(entity).profile.tap do |profile| author_of(entity).profile.tap do |profile|
@ -115,8 +131,10 @@ module Diaspora
end end
def self.status_message(entity) def self.status_message(entity)
author = author_of(entity)
try_load_existing_guid(StatusMessage, entity.guid, author) do
StatusMessage.new( StatusMessage.new(
author: author_of(entity), author: author,
guid: entity.guid, guid: entity.guid,
raw_message: entity.raw_message, raw_message: entity.raw_message,
public: entity.public, public: entity.public,
@ -130,12 +148,12 @@ module Diaspora
status_message.save! status_message.save!
end end
end end
end
private
def self.author_of(entity) def self.author_of(entity)
Person.find_by(diaspora_handle: entity.author) Person.find_by(diaspora_handle: entity.author)
end end
private_class_method :author_of
def self.build_location(entity) def self.build_location(entity)
Location.new( Location.new(
@ -144,6 +162,7 @@ module Diaspora
lng: entity.lng lng: entity.lng
) )
end end
private_class_method :build_location
def self.build_message(entity) def self.build_message(entity)
Message.new( Message.new(
@ -154,6 +173,7 @@ module Diaspora
conversation_guid: entity.conversation_guid conversation_guid: entity.conversation_guid
) )
end end
private_class_method :build_message
def self.build_photo(entity) def self.build_photo(entity)
Photo.new( Photo.new(
@ -169,6 +189,7 @@ module Diaspora
width: entity.width width: entity.width
) )
end end
private_class_method :build_photo
def self.build_poll(entity) def self.build_poll(entity)
Poll.new( Poll.new(
@ -183,6 +204,7 @@ module Diaspora
end end
end end
end end
private_class_method :build_poll
def self.save_relayable(relayable, entity) def self.save_relayable(relayable, entity)
retract_if_author_ignored(relayable) retract_if_author_ignored(relayable)
@ -190,6 +212,7 @@ module Diaspora
relayable.author_signature = entity.author_signature if relayable.parent.author.local? relayable.author_signature = entity.author_signature if relayable.parent.author.local?
relayable.save! relayable.save!
end end
private_class_method :save_relayable
def self.retract_if_author_ignored(relayable) def self.retract_if_author_ignored(relayable)
parent_author = relayable.parent.author parent_author = relayable.parent.author
@ -199,6 +222,45 @@ module Diaspora
raise Diaspora::Federation::AuthorIgnored raise Diaspora::Federation::AuthorIgnored
end end
private_class_method :retract_if_author_ignored
# check if the object already exists, otherwise save it.
# if save fails (probably because of a second object received parallel),
# check again if an object with the same guid already exists, but maybe has a different author.
# @raise [InvalidAuthor] if the author of the existing object doesn't match
def self.ignore_existing_guid(klass, guid, author)
yield unless klass.where(guid: guid, author_id: author.id).exists?
rescue => e
raise e unless load_from_database(klass, guid, author)
logger.warn "ignoring error on receive #{klass}:#{guid}: #{e.class}: #{e.message}"
end
private_class_method :ignore_existing_guid
# try to load the object first from the DB and if not available, save it.
# if save fails (probably because of a second object received parallel),
# try again to load it, because it is possibly there now.
# @raise [InvalidAuthor] if the author of the existing object doesn't match
def self.try_load_existing_guid(klass, guid, author)
load_from_database(klass, guid, author) || yield
rescue Diaspora::Federation::InvalidAuthor => e
raise e # don't try loading from db twice
rescue => e
logger.warn "failed to save #{klass}:#{guid} (#{e.class}: #{e.message}) - try loading it from DB"
load_from_database(klass, guid, author).tap do |object|
raise e unless object
end
end
private_class_method :try_load_existing_guid
# @raise [InvalidAuthor] if the author of the loaded object doesn't match
def self.load_from_database(klass, guid, author)
klass.find_by(guid: guid).tap do |object|
if object && object.author_id != author.id
raise Diaspora::Federation::InvalidAuthor, "#{klass}:#{guid}: #{author.diaspora_handle}"
end
end
end
private_class_method :load_from_database
end end
end end
end end