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
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

View file

@ -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

View file

@ -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<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
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<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

View file

@ -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

View file

@ -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

View file

@ -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