diff --git a/lib/postzord/receiver/local_batch.rb b/lib/postzord/receiver/local_batch.rb index 121f04fa9..f2da0380d 100644 --- a/lib/postzord/receiver/local_batch.rb +++ b/lib/postzord/receiver/local_batch.rb @@ -1,71 +1,75 @@ -module Postzord - module Receiver - class LocalBatch - attr_reader :object, :recipient_user_ids, :users +# Copyright (c) 2010-2011, Diaspora Inc. This file is +# licensed under the Affero General Public License version 3 or later. See +# the COPYRIGHT file. - def initialize(object, recipient_user_ids) - @object = object - @recipient_user_ids = recipient_user_ids - @users = User.where(:id => @recipient_user_ids) - end +class Postzord::Receiver::LocalBatch < Postzord::Receiver - def perform! - if @object.respond_to?(:relayable?) - receive_relayable - else - create_post_visibilities - end - notify_mentioned_users if @object.respond_to?(:mentions) + attr_reader :object, :recipient_user_ids, :users - # 09/27/11 this is slow - #socket_to_users if @object.respond_to?(:socket_to_user) - notify_users - end + def initialize(object, recipient_user_ids) + @object = object + @recipient_user_ids = recipient_user_ids + @users = User.where(:id => @recipient_user_ids) + end - # NOTE(copied over from receiver public) - # @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) - end - @object - end + def perform! + if @object.respond_to?(:relayable?) + receive_relayable + else + create_post_visibilities - # Batch import post visibilities for the recipients of the given @object - # @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 + # if caching enabled, add to cache - # 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 + end + notify_mentioned_users if @object.respond_to?(:mentions) - #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 - # @return [void] - def socket_to_users - @users.each do |user| - @object.socket_to_user(user) - end - end + # NOTE(copied over from receiver public) + # @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) + end + @object + 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 + # Batch import post visibilities for the recipients of the given @object + # @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 + # @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 diff --git a/lib/postzord/receiver/private.rb b/lib/postzord/receiver/private.rb index 2ab9ed6e6..03c799928 100644 --- a/lib/postzord/receiver/private.rb +++ b/lib/postzord/receiver/private.rb @@ -1,117 +1,114 @@ # Copyright (c) 2010-2011, Diaspora Inc. This file is # licensed under the Affero General Public License version 3 or later. See # the COPYRIGHT file. -# + require File.join(Rails.root, 'lib/webfinger') require File.join(Rails.root, 'lib/diaspora/parser') -module Postzord - module Receiver - class Private - def initialize(user, opts={}) - @user = user - @user_person = @user.person - @salmon_xml = opts[:salmon_xml] +class Postzord::Receiver::Private < Postzord::Receiver - @sender = opts[:person] || Webfinger.new(self.salmon.author_id).fetch - @author = @sender + def initialize(user, opts={}) + @user = user + @user_person = @user.person + @salmon_xml = opts[:salmon_xml] - @object = opts[:object] - end + @sender = opts[:person] || Webfinger.new(self.salmon.author_id).fetch + @author = @sender - def perform - 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 + @object = opts[:object] + end - def parse_and_receive(xml) - @object ||= Diaspora::Parser.from_xml(xml) + def perform! + 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 - set_author! - receive_object - else - raise "not a valid object:#{@object.inspect}" - end - end + Rails.logger.info("event=receive status=start recipient=#{@user_person.diaspora_handle} payload_type=#{@object.class} sender=#{@sender.diaspora_handle}") - # @return [Object] - def receive_object - obj = @object.receive(@user, @author) - 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 + if self.validate_object + set_author! + receive_object + else + raise "not a valid object:#{@object.inspect}" + end + end - protected - def salmon - @salmon ||= Salmon::EncryptedSlap.from_xml(@salmon_xml, @user) - end + # @return [Object] + def receive_object + obj = @object.receive(@user, @author) + 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 - if @object.respond_to?(:relayable?) - #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 + protected + def salmon + @salmon ||= Salmon::EncryptedSlap.from_xml(@salmon_xml, @user) + end - def validate_object - return false if contact_required_unless_request - return false if relayable_without_parent? + def xml_author + if @object.respond_to?(:relayable?) + #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 - end + return false if author_does_not_match_xml_author? - def set_author! - return unless @author - @object.author = @author if @object.respond_to? :author= - @object.person = @author if @object.respond_to? :person= - end + @object + 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 - 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 + private - def author_does_not_match_xml_author? - if (@author.diaspora_handle != xml_author) - 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 - end - end + #validations + 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 contact_required_unless_request - unless @object.is_a?(Request) || @user.contact_for(@sender) - Rails.logger.info("event=receive status=abort reason='sender not connected to recipient' recipient=#{@user_person.diaspora_handle} sender=#{@sender.diaspora_handle}") - return true - end - end + def author_does_not_match_xml_author? + if (@author.diaspora_handle != xml_author) + 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 + end + end - def assign_sender_handle_if_request - #special casey - if @object.is_a?(Request) - @object.sender_handle = @sender.diaspora_handle - end - end + def contact_required_unless_request + unless @object.is_a?(Request) || @user.contact_for(@sender) + Rails.logger.info("event=receive status=abort reason='sender not connected to recipient' recipient=#{@user_person.diaspora_handle} sender=#{@sender.diaspora_handle}") + return true + end + end + + def assign_sender_handle_if_request + #special casey + if @object.is_a?(Request) + @object.sender_handle = @sender.diaspora_handle end end end diff --git a/lib/postzord/receiver/public.rb b/lib/postzord/receiver/public.rb index 0fc4374d4..955f97299 100644 --- a/lib/postzord/receiver/public.rb +++ b/lib/postzord/receiver/public.rb @@ -2,63 +2,60 @@ # licensed under the Affero General Public License version 3 or later. See # the COPYRIGHT file. -module Postzord - module Receiver - class Public - attr_accessor :salmon, :author +class Postzord::Receiver::Public < Postzord::Receiver - def initialize(xml) - @salmon = Salmon::Slap.from_xml(xml) - @author = Webfinger.new(@salmon.author_id).fetch - end + attr_accessor :salmon, :author - # @return [Boolean] - def verified_signature? - @salmon.verified_for_key?(@author.public_key) - end + def initialize(xml) + @salmon = Salmon::Slap.from_xml(xml) + @author = Webfinger.new(@salmon.author_id).fetch + end - # @return [void] - def perform! - return false unless verified_signature? - return unless save_object + # @return [Boolean] + def verified_signature? + @salmon.verified_for_key?(@author.public_key) + end - if @object.respond_to?(:relayable?) - receive_relayable - else - Resque.enqueue(Jobs::ReceiveLocalBatch, @object.class.to_s, @object.id, self.recipient_user_ids) - end - end + # @return [void] + def perform! + return false unless verified_signature? + return unless save_object - # @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] 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 + if @object.respond_to?(:relayable?) + receive_relayable + else + Resque.enqueue(Jobs::ReceiveLocalBatch, @object.class.to_s, @object.id, self.recipient_user_ids) 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] 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 diff --git a/spec/integration/attack_vectors_spec.rb b/spec/integration/attack_vectors_spec.rb index 81dd26faa..cfa8b47ad 100644 --- a/spec/integration/attack_vectors_spec.rb +++ b/spec/integration/attack_vectors_spec.rb @@ -22,7 +22,7 @@ describe "attack vectors" do zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) expect { - zord.perform + zord.perform! }.should raise_error /not a valid object/ 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) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) expect { - zord.perform + zord.perform! }.should raise_error /not a valid object/ 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) 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) salmon_xml = alice.salmon(malicious_message).xml_for(bob.person) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) - zord.perform + zord.perform! original_message.reload.text.should == "store this!" end @@ -68,14 +68,14 @@ describe "attack vectors" do salmon_xml = eve.salmon(original_message).xml_for(bob.person) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) - zord.perform + zord.perform! lambda { 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) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) - zord.perform + zord.perform! }.should_not change{ bob.reload.visible_posts.count @@ -97,7 +97,7 @@ describe "attack vectors" do zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) expect { - zord.perform + zord.perform! }.should raise_error /not a valid object/ 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) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) - zord.perform + zord.perform! bob.visible_posts.count.should == 1 StatusMessage.count.should == 1 @@ -121,7 +121,7 @@ describe "attack vectors" do salmon_xml = alice.salmon(ret).xml_for(bob.person) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) - zord.perform + zord.perform! StatusMessage.count.should == 1 bob.visible_posts.count.should == 1 @@ -143,7 +143,7 @@ describe "attack vectors" do proc { salmon_xml = alice.salmon(ret).xml_for(bob.person) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) - zord.perform + zord.perform! }.should_not raise_error end @@ -152,7 +152,7 @@ describe "attack vectors" do salmon_xml = eve.salmon(original_message).xml_for(bob.person) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) - zord.perform + zord.perform! bob.visible_posts.count.should == 1 @@ -164,7 +164,7 @@ describe "attack vectors" do salmon_xml = alice.salmon(ret).xml_for(bob.person) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) expect { - zord.perform + zord.perform! }.should raise_error /not a valid object/ bob.reload.visible_posts.count.should == 1 @@ -180,7 +180,7 @@ describe "attack vectors" do salmon_xml = alice.salmon(ret).xml_for(bob.person) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) - zord.perform + zord.perform! }.should_not change{bob.reload.contacts.count} end @@ -196,7 +196,7 @@ describe "attack vectors" do salmon_xml = alice.salmon(ret).xml_for(bob.person) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) expect { - zord.perform + zord.perform! }.should raise_error /not a valid object/ bob.reload.contacts.count.should == 2 @@ -207,7 +207,7 @@ describe "attack vectors" do salmon_xml = eve.salmon(original_message).xml_for(bob.person) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) - zord.perform + zord.perform! original_message.diaspora_handle = alice.diaspora_handle original_message.text= "bad bad bad" @@ -215,7 +215,7 @@ describe "attack vectors" do salmon_xml = alice.salmon(original_message).xml_for(bob.person) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) - zord.perform + zord.perform! original_message.reload.text.should == "store this!" end diff --git a/spec/integration/receiving_spec.rb b/spec/integration/receiving_spec.rb index f6d8f4f5f..2e18f2c87 100644 --- a/spec/integration/receiving_spec.rb +++ b/spec/integration/receiving_spec.rb @@ -334,7 +334,7 @@ describe 'a user receives a post' do salmon_xml = salmon.xml_for(bob.person) zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml) - zord.perform + zord.perform! bob.visible_posts.include?(post).should be_true end diff --git a/spec/lib/postzord/receiver/private_spec.rb b/spec/lib/postzord/receiver/private_spec.rb index 636b9f220..f86eb6a64 100644 --- a/spec/lib/postzord/receiver/private_spec.rb +++ b/spec/lib/postzord/receiver/private_spec.rb @@ -47,7 +47,7 @@ describe Postzord::Receiver::Private do end end - describe '#perform' do + describe '#perform!' do before do @zord = Postzord::Receiver::Private.new(@user, :salmon_xml => @salmon_xml) @salmon = @zord.instance_variable_get(:@salmon) @@ -56,25 +56,25 @@ describe Postzord::Receiver::Private do context 'returns nil' do it 'if the salmon author does not exist' do @zord.instance_variable_set(:@sender, nil) - @zord.perform.should be_nil + @zord.perform!.should be_nil end it 'if the author does not match the signature' do @zord.instance_variable_set(:@sender, Factory(:person)) - @zord.perform.should be_nil + @zord.perform!.should be_nil end end context 'returns the sent object' do it 'returns the received object on success' do - object = @zord.perform + object = @zord.perform! object.should respond_to(:to_diaspora_xml) end end it 'parses the salmon object' do Diaspora::Parser.should_receive(:from_xml).with(@salmon.parsed_data).and_return(@original_post) - @zord.perform + @zord.perform! end end