subclassing receivers, renamed perform to perform [bang]

This commit is contained in:
Ilya Zhitomirskiy 2011-09-30 16:45:01 -07:00
parent 4a8aea9e78
commit 7926ebfb53
6 changed files with 220 additions and 222 deletions

View file

@ -1,71 +1,75 @@
module Postzord # Copyright (c) 2010-2011, Diaspora Inc. This file is
module Receiver # licensed under the Affero General Public License version 3 or later. See
class LocalBatch # the COPYRIGHT file.
attr_reader :object, :recipient_user_ids, :users
def initialize(object, recipient_user_ids) class Postzord::Receiver::LocalBatch < Postzord::Receiver
@object = object
@recipient_user_ids = recipient_user_ids
@users = User.where(:id => @recipient_user_ids)
end
def perform! attr_reader :object, :recipient_user_ids, :users
if @object.respond_to?(:relayable?)
receive_relayable
else
create_post_visibilities
end
notify_mentioned_users if @object.respond_to?(:mentions)
# 09/27/11 this is slow def initialize(object, recipient_user_ids)
#socket_to_users if @object.respond_to?(:socket_to_user) @object = object
notify_users @recipient_user_ids = recipient_user_ids
end @users = User.where(:id => @recipient_user_ids)
end
# NOTE(copied over from receiver public) def perform!
# @return [Object] if @object.respond_to?(:relayable?)
def receive_relayable receive_relayable
if @object.parent.author.local? else
# receive relayable object only for the owner of the parent object create_post_visibilities
@object.receive(@object.parent.author.owner)
end
@object
end
# Batch import post visibilities for the recipients of the given @object # if caching enabled, add to cache
# @note performs a bulk insert into mySQL
# @return [void]
def create_post_visibilities
contacts_ids = Contact.connection.select_values(Contact.where(:user_id => @recipient_user_ids, :person_id => @object.author_id).select("id").to_sql)
PostVisibility.batch_import(contacts_ids, object)
end
# Notify any mentioned users within the @object's text end
# @return [void] notify_mentioned_users if @object.respond_to?(:mentions)
def notify_mentioned_users
@object.mentions.each do |mention|
mention.notify_recipient
end
end
#NOTE(these methods should be in their own module, included in this class) # 09/27/11 this is slow
#socket_to_users if @object.respond_to?(:socket_to_user)
notify_users
end
# Issue websocket requests to all specified recipients # NOTE(copied over from receiver public)
# @return [void] # @return [Object]
def socket_to_users def receive_relayable
@users.each do |user| if @object.parent.author.local?
@object.socket_to_user(user) # receive relayable object only for the owner of the parent object
end @object.receive(@object.parent.author.owner)
end end
@object
end
# Notify users of the new object # Batch import post visibilities for the recipients of the given @object
# return [void] # @note performs a bulk insert into mySQL
def notify_users # @return [void]
return unless @object.respond_to?(:notification_type) def create_post_visibilities
@users.each do |user| contacts_ids = Contact.connection.select_values(Contact.where(:user_id => @recipient_user_ids, :person_id => @object.author_id).select("id").to_sql)
Notification.notify(user, @object, @object.author) PostVisibility.batch_import(contacts_ids, object)
end end
end
# Notify any mentioned users within the @object's text
# @return [void]
def notify_mentioned_users
@object.mentions.each do |mention|
mention.notify_recipient
end
end
#NOTE(these methods should be in their own module, included in this class)
# Issue websocket requests to all specified recipients
# @return [void]
def socket_to_users
@users.each do |user|
@object.socket_to_user(user)
end
end
# Notify users of the new object
# return [void]
def notify_users
return unless @object.respond_to?(:notification_type)
@users.each do |user|
Notification.notify(user, @object, @object.author)
end end
end end
end end

View file

@ -1,117 +1,114 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is # Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See # licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file. # the COPYRIGHT file.
#
require File.join(Rails.root, 'lib/webfinger') require File.join(Rails.root, 'lib/webfinger')
require File.join(Rails.root, 'lib/diaspora/parser') require File.join(Rails.root, 'lib/diaspora/parser')
module Postzord class Postzord::Receiver::Private < Postzord::Receiver
module Receiver
class Private
def initialize(user, opts={})
@user = user
@user_person = @user.person
@salmon_xml = opts[:salmon_xml]
@sender = opts[:person] || Webfinger.new(self.salmon.author_id).fetch def initialize(user, opts={})
@author = @sender @user = user
@user_person = @user.person
@salmon_xml = opts[:salmon_xml]
@object = opts[:object] @sender = opts[:person] || Webfinger.new(self.salmon.author_id).fetch
end @author = @sender
def perform @object = opts[:object]
if @sender && self.salmon.verified_for_key?(@sender.public_key) end
parse_and_receive(salmon.parsed_data)
else
Rails.logger.info("event=receive status=abort recipient=#{@user.diaspora_handle} sender=#{@salmon.author_id} reason='not_verified for key'")
nil
end
end
def parse_and_receive(xml) def perform!
@object ||= Diaspora::Parser.from_xml(xml) if @sender && self.salmon.verified_for_key?(@sender.public_key)
parse_and_receive(salmon.parsed_data)
else
Rails.logger.info("event=receive status=abort recipient=#{@user.diaspora_handle} sender=#{@salmon.author_id} reason='not_verified for key'")
nil
end
end
Rails.logger.info("event=receive status=start recipient=#{@user_person.diaspora_handle} payload_type=#{@object.class} sender=#{@sender.diaspora_handle}") def parse_and_receive(xml)
@object ||= Diaspora::Parser.from_xml(xml)
if self.validate_object Rails.logger.info("event=receive status=start recipient=#{@user_person.diaspora_handle} payload_type=#{@object.class} sender=#{@sender.diaspora_handle}")
set_author!
receive_object
else
raise "not a valid object:#{@object.inspect}"
end
end
# @return [Object] if self.validate_object
def receive_object set_author!
obj = @object.receive(@user, @author) receive_object
Notification.notify(@user, obj, @author) if obj.respond_to?(:notification_type) else
Rails.logger.info("event=receive status=complete recipient=#{@user_person.diaspora_handle} sender=#{@sender.diaspora_handle} payload_type=#{obj.class}") raise "not a valid object:#{@object.inspect}"
obj end
end end
protected # @return [Object]
def salmon def receive_object
@salmon ||= Salmon::EncryptedSlap.from_xml(@salmon_xml, @user) obj = @object.receive(@user, @author)
end Notification.notify(@user, obj, @author) if obj.respond_to?(:notification_type)
Rails.logger.info("event=receive status=complete recipient=#{@user_person.diaspora_handle} sender=#{@sender.diaspora_handle} payload_type=#{obj.class}")
obj
end
def xml_author protected
if @object.respond_to?(:relayable?) def salmon
#if A and B are friends, and A sends B a comment from C, we delegate the validation to the owner of the post being commented on @salmon ||= Salmon::EncryptedSlap.from_xml(@salmon_xml, @user)
xml_author = @user.owns?(@object.parent) ? @object.diaspora_handle : @object.parent.author.diaspora_handle end
@author = Webfinger.new(@object.diaspora_handle).fetch if @object.author
else
xml_author = @object.diaspora_handle
end
xml_author
end
def validate_object def xml_author
return false if contact_required_unless_request if @object.respond_to?(:relayable?)
return false if relayable_without_parent? #if A and B are friends, and A sends B a comment from C, we delegate the validation to the owner of the post being commented on
xml_author = @user.owns?(@object.parent) ? @object.diaspora_handle : @object.parent.author.diaspora_handle
@author = Webfinger.new(@object.diaspora_handle).fetch if @object.author
else
xml_author = @object.diaspora_handle
end
xml_author
end
assign_sender_handle_if_request def validate_object
return false if contact_required_unless_request
return false if relayable_without_parent?
return false if author_does_not_match_xml_author? assign_sender_handle_if_request
@object return false if author_does_not_match_xml_author?
end
def set_author! @object
return unless @author end
@object.author = @author if @object.respond_to? :author=
@object.person = @author if @object.respond_to? :person=
end
private def set_author!
return unless @author
@object.author = @author if @object.respond_to? :author=
@object.person = @author if @object.respond_to? :person=
end
#validations private
def relayable_without_parent?
if @object.respond_to?(:relayable?) && @object.parent.nil?
Rails.logger.info("event=receive status=abort reason='received a comment but no corresponding post' recipient=#{@user_person.diaspora_handle} sender=#{@sender.diaspora_handle} payload_type=#{@object.class})")
return true
end
end
def author_does_not_match_xml_author? #validations
if (@author.diaspora_handle != xml_author) def relayable_without_parent?
Rails.logger.info("event=receive status=abort reason='author in xml does not match retrieved person' payload_type=#{@object.class} recipient=#{@user_person.diaspora_handle} sender=#{@sender.diaspora_handle}") if @object.respond_to?(:relayable?) && @object.parent.nil?
return true Rails.logger.info("event=receive status=abort reason='received a comment but no corresponding post' recipient=#{@user_person.diaspora_handle} sender=#{@sender.diaspora_handle} payload_type=#{@object.class})")
end return true
end end
end
def contact_required_unless_request def author_does_not_match_xml_author?
unless @object.is_a?(Request) || @user.contact_for(@sender) if (@author.diaspora_handle != xml_author)
Rails.logger.info("event=receive status=abort reason='sender not connected to recipient' recipient=#{@user_person.diaspora_handle} sender=#{@sender.diaspora_handle}") Rails.logger.info("event=receive status=abort reason='author in xml does not match retrieved person' payload_type=#{@object.class} recipient=#{@user_person.diaspora_handle} sender=#{@sender.diaspora_handle}")
return true return true
end end
end end
def assign_sender_handle_if_request def contact_required_unless_request
#special casey unless @object.is_a?(Request) || @user.contact_for(@sender)
if @object.is_a?(Request) Rails.logger.info("event=receive status=abort reason='sender not connected to recipient' recipient=#{@user_person.diaspora_handle} sender=#{@sender.diaspora_handle}")
@object.sender_handle = @sender.diaspora_handle return true
end end
end end
def assign_sender_handle_if_request
#special casey
if @object.is_a?(Request)
@object.sender_handle = @sender.diaspora_handle
end end
end end
end end

View file

@ -2,63 +2,60 @@
# licensed under the Affero General Public License version 3 or later. See # licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file. # the COPYRIGHT file.
module Postzord class Postzord::Receiver::Public < Postzord::Receiver
module Receiver
class Public
attr_accessor :salmon, :author
def initialize(xml) attr_accessor :salmon, :author
@salmon = Salmon::Slap.from_xml(xml)
@author = Webfinger.new(@salmon.author_id).fetch
end
# @return [Boolean] def initialize(xml)
def verified_signature? @salmon = Salmon::Slap.from_xml(xml)
@salmon.verified_for_key?(@author.public_key) @author = Webfinger.new(@salmon.author_id).fetch
end end
# @return [void] # @return [Boolean]
def perform! def verified_signature?
return false unless verified_signature? @salmon.verified_for_key?(@author.public_key)
return unless save_object end
if @object.respond_to?(:relayable?) # @return [void]
receive_relayable def perform!
else return false unless verified_signature?
Resque.enqueue(Jobs::ReceiveLocalBatch, @object.class.to_s, @object.id, self.recipient_user_ids) return unless save_object
end
end
# @return [Object] if @object.respond_to?(:relayable?)
def receive_relayable receive_relayable
if @object.parent.author.local? else
# receive relayable object only for the owner of the parent object Resque.enqueue(Jobs::ReceiveLocalBatch, @object.class.to_s, @object.id, self.recipient_user_ids)
@object.receive(@object.parent.author.owner, @author)
end
# notify everyone who can see the parent object
receiver = Postzord::Receiver::LocalBatch.new(@object, self.recipient_user_ids)
receiver.notify_users
@object
end
# @return [Object]
def save_object
@object = Diaspora::Parser::from_xml(@salmon.parsed_data)
raise "Object is not public" if object_can_be_public_and_it_is_not?
@object.save!
end
# @return [Array<Integer>] User ids
def recipient_user_ids
User.all_sharing_with_person(@author).select('users.id').map!{ |u| u.id }
end
private
# @return [Boolean]
def object_can_be_public_and_it_is_not?
@object.respond_to?(:public) && !@object.public?
end
end end
end end
# @return [Object]
def receive_relayable
if @object.parent.author.local?
# receive relayable object only for the owner of the parent object
@object.receive(@object.parent.author.owner, @author)
end
# notify everyone who can see the parent object
receiver = Postzord::Receiver::LocalBatch.new(@object, self.recipient_user_ids)
receiver.notify_users
@object
end
# @return [Object]
def save_object
@object = Diaspora::Parser::from_xml(@salmon.parsed_data)
raise "Object is not public" if object_can_be_public_and_it_is_not?
@object.save!
end
# @return [Array<Integer>] User ids
def recipient_user_ids
User.all_sharing_with_person(@author).select('users.id').map!{ |u| u.id }
end
private
# @return [Boolean]
def object_can_be_public_and_it_is_not?
@object.respond_to?(:public) && !@object.public?
end
end end

View file

@ -22,7 +22,7 @@ describe "attack vectors" do
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
expect { expect {
zord.perform zord.perform!
}.should raise_error /not a valid object/ }.should raise_error /not a valid object/
bob.visible_posts.include?(post_from_non_contact).should be_false bob.visible_posts.include?(post_from_non_contact).should be_false
@ -39,7 +39,7 @@ describe "attack vectors" do
salmon_xml = bob.salmon(original_message).xml_for(alice.person) salmon_xml = bob.salmon(original_message).xml_for(alice.person)
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
expect { expect {
zord.perform zord.perform!
}.should raise_error /not a valid object/ }.should raise_error /not a valid object/
alice.reload.visible_posts.should_not include(StatusMessage.find(original_message.id)) alice.reload.visible_posts.should_not include(StatusMessage.find(original_message.id))
@ -53,12 +53,12 @@ describe "attack vectors" do
salmon_xml = eve.salmon(original_message).xml_for(bob.person) salmon_xml = eve.salmon(original_message).xml_for(bob.person)
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
zord.perform zord.perform!
malicious_message = Factory.build(:status_message, :id => original_message.id, :text => 'BAD!!!', :author => alice.person) malicious_message = Factory.build(:status_message, :id => original_message.id, :text => 'BAD!!!', :author => alice.person)
salmon_xml = alice.salmon(malicious_message).xml_for(bob.person) salmon_xml = alice.salmon(malicious_message).xml_for(bob.person)
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
zord.perform zord.perform!
original_message.reload.text.should == "store this!" original_message.reload.text.should == "store this!"
end end
@ -68,14 +68,14 @@ describe "attack vectors" do
salmon_xml = eve.salmon(original_message).xml_for(bob.person) salmon_xml = eve.salmon(original_message).xml_for(bob.person)
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
zord.perform zord.perform!
lambda { lambda {
malicious_message = Factory.build( :status_message, :id => original_message.id, :text => 'BAD!!!', :author => eve.person) malicious_message = Factory.build( :status_message, :id => original_message.id, :text => 'BAD!!!', :author => eve.person)
salmon_xml2 = alice.salmon(malicious_message).xml_for(bob.person) salmon_xml2 = alice.salmon(malicious_message).xml_for(bob.person)
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
zord.perform zord.perform!
}.should_not change{ }.should_not change{
bob.reload.visible_posts.count bob.reload.visible_posts.count
@ -97,7 +97,7 @@ describe "attack vectors" do
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
expect { expect {
zord.perform zord.perform!
}.should raise_error /not a valid object/ }.should raise_error /not a valid object/
eve.reload.profile.first_name.should == first_name eve.reload.profile.first_name.should == first_name
@ -109,7 +109,7 @@ describe "attack vectors" do
salmon_xml = eve.salmon(original_message).xml_for(bob.person) salmon_xml = eve.salmon(original_message).xml_for(bob.person)
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
zord.perform zord.perform!
bob.visible_posts.count.should == 1 bob.visible_posts.count.should == 1
StatusMessage.count.should == 1 StatusMessage.count.should == 1
@ -121,7 +121,7 @@ describe "attack vectors" do
salmon_xml = alice.salmon(ret).xml_for(bob.person) salmon_xml = alice.salmon(ret).xml_for(bob.person)
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
zord.perform zord.perform!
StatusMessage.count.should == 1 StatusMessage.count.should == 1
bob.visible_posts.count.should == 1 bob.visible_posts.count.should == 1
@ -143,7 +143,7 @@ describe "attack vectors" do
proc { proc {
salmon_xml = alice.salmon(ret).xml_for(bob.person) salmon_xml = alice.salmon(ret).xml_for(bob.person)
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
zord.perform zord.perform!
}.should_not raise_error }.should_not raise_error
end end
@ -152,7 +152,7 @@ describe "attack vectors" do
salmon_xml = eve.salmon(original_message).xml_for(bob.person) salmon_xml = eve.salmon(original_message).xml_for(bob.person)
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
zord.perform zord.perform!
bob.visible_posts.count.should == 1 bob.visible_posts.count.should == 1
@ -164,7 +164,7 @@ describe "attack vectors" do
salmon_xml = alice.salmon(ret).xml_for(bob.person) salmon_xml = alice.salmon(ret).xml_for(bob.person)
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
expect { expect {
zord.perform zord.perform!
}.should raise_error /not a valid object/ }.should raise_error /not a valid object/
bob.reload.visible_posts.count.should == 1 bob.reload.visible_posts.count.should == 1
@ -180,7 +180,7 @@ describe "attack vectors" do
salmon_xml = alice.salmon(ret).xml_for(bob.person) salmon_xml = alice.salmon(ret).xml_for(bob.person)
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
zord.perform zord.perform!
}.should_not change{bob.reload.contacts.count} }.should_not change{bob.reload.contacts.count}
end end
@ -196,7 +196,7 @@ describe "attack vectors" do
salmon_xml = alice.salmon(ret).xml_for(bob.person) salmon_xml = alice.salmon(ret).xml_for(bob.person)
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
expect { expect {
zord.perform zord.perform!
}.should raise_error /not a valid object/ }.should raise_error /not a valid object/
bob.reload.contacts.count.should == 2 bob.reload.contacts.count.should == 2
@ -207,7 +207,7 @@ describe "attack vectors" do
salmon_xml = eve.salmon(original_message).xml_for(bob.person) salmon_xml = eve.salmon(original_message).xml_for(bob.person)
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
zord.perform zord.perform!
original_message.diaspora_handle = alice.diaspora_handle original_message.diaspora_handle = alice.diaspora_handle
original_message.text= "bad bad bad" original_message.text= "bad bad bad"
@ -215,7 +215,7 @@ describe "attack vectors" do
salmon_xml = alice.salmon(original_message).xml_for(bob.person) salmon_xml = alice.salmon(original_message).xml_for(bob.person)
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
zord.perform zord.perform!
original_message.reload.text.should == "store this!" original_message.reload.text.should == "store this!"
end end

View file

@ -334,7 +334,7 @@ describe 'a user receives a post' do
salmon_xml = salmon.xml_for(bob.person) salmon_xml = salmon.xml_for(bob.person)
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
zord.perform zord.perform!
bob.visible_posts.include?(post).should be_true bob.visible_posts.include?(post).should be_true
end end

View file

@ -47,7 +47,7 @@ describe Postzord::Receiver::Private do
end end
end end
describe '#perform' do describe '#perform!' do
before do before do
@zord = Postzord::Receiver::Private.new(@user, :salmon_xml => @salmon_xml) @zord = Postzord::Receiver::Private.new(@user, :salmon_xml => @salmon_xml)
@salmon = @zord.instance_variable_get(:@salmon) @salmon = @zord.instance_variable_get(:@salmon)
@ -56,25 +56,25 @@ describe Postzord::Receiver::Private do
context 'returns nil' do context 'returns nil' do
it 'if the salmon author does not exist' do it 'if the salmon author does not exist' do
@zord.instance_variable_set(:@sender, nil) @zord.instance_variable_set(:@sender, nil)
@zord.perform.should be_nil @zord.perform!.should be_nil
end end
it 'if the author does not match the signature' do it 'if the author does not match the signature' do
@zord.instance_variable_set(:@sender, Factory(:person)) @zord.instance_variable_set(:@sender, Factory(:person))
@zord.perform.should be_nil @zord.perform!.should be_nil
end end
end end
context 'returns the sent object' do context 'returns the sent object' do
it 'returns the received object on success' do it 'returns the received object on success' do
object = @zord.perform object = @zord.perform!
object.should respond_to(:to_diaspora_xml) object.should respond_to(:to_diaspora_xml)
end end
end end
it 'parses the salmon object' do it 'parses the salmon object' do
Diaspora::Parser.should_receive(:from_xml).with(@salmon.parsed_data).and_return(@original_post) Diaspora::Parser.should_receive(:from_xml).with(@salmon.parsed_data).and_return(@original_post)
@zord.perform @zord.perform!
end end
end end