diff --git a/app/controllers/diaspora_federation/receive_controller.rb b/app/controllers/diaspora_federation/receive_controller.rb index 733cf4d..4a41ad2 100644 --- a/app/controllers/diaspora_federation/receive_controller.rb +++ b/app/controllers/diaspora_federation/receive_controller.rb @@ -1,4 +1,5 @@ require_dependency "diaspora_federation/application_controller" +require "diaspora_federation/receiver" module DiasporaFederation # this controller processes receiving messages @@ -11,6 +12,7 @@ module DiasporaFederation def public logger.info "received a public message" logger.debug CGI.unescape(params[:xml]) + Receiver::Public.new(CGI.unescape(params[:xml])).receive! render nothing: true, status: :ok end @@ -20,7 +22,12 @@ module DiasporaFederation def private logger.info "received a private message for #{params[:guid]}" logger.debug CGI.unescape(params[:xml]) - render nothing: true, status: :ok + begin + Receiver::Private.new(params[:guid], CGI.unescape(params[:xml])).receive! + render nothing: true, status: :ok + rescue RecipientNotFound + render nothing: true, status: 404 + end end private diff --git a/lib/diaspora_federation.rb b/lib/diaspora_federation.rb index 3390be0..73f9204 100644 --- a/lib/diaspora_federation.rb +++ b/lib/diaspora_federation.rb @@ -12,6 +12,7 @@ require "diaspora_federation/entities" require "diaspora_federation/discovery" require "diaspora_federation/salmon" +require "diaspora_federation/receiver" # diaspora* federation library module DiasporaFederation @@ -22,11 +23,13 @@ module DiasporaFederation fetch_person_for_hcard save_person_after_webfinger fetch_private_key_by_diaspora_id + fetch_private_key_by_user_guid fetch_author_private_key_by_entity_guid fetch_public_key_by_diaspora_id fetch_author_public_key_by_entity_guid entity_author_is_local? fetch_entity_author_id_by_guid + entity_persist ) class << self diff --git a/lib/diaspora_federation/receiver.rb b/lib/diaspora_federation/receiver.rb new file mode 100644 index 0000000..eca48eb --- /dev/null +++ b/lib/diaspora_federation/receiver.rb @@ -0,0 +1,24 @@ +module DiasporaFederation + # SenderNotFound is raised when failed to fetch a public key of the sender of the received message + class SenderNotFound < Exception + end + + # Common base for Private and Public receivers + # @see Receiver::Public + # @see Receiver::Private + class Receiver + def initialize(salmon_xml) + @salmon_xml = salmon_xml + end + + def receive! + sender_id = slap.author_id + pkey = DiasporaFederation.callbacks.trigger(:fetch_public_key_by_diaspora_id, sender_id) + raise SenderNotFound if pkey.nil? + DiasporaFederation.callbacks.trigger(:entity_persist, slap.entity(pkey), @recipient_guid, sender_id) + end + end +end + +require "diaspora_federation/receiver/private" +require "diaspora_federation/receiver/public" diff --git a/lib/diaspora_federation/receiver/private.rb b/lib/diaspora_federation/receiver/private.rb new file mode 100644 index 0000000..789355d --- /dev/null +++ b/lib/diaspora_federation/receiver/private.rb @@ -0,0 +1,24 @@ +module DiasporaFederation + # RecipientNotFound is raised when failed to fetch a private key of the recipient of the received message + class RecipientNotFound < Exception + end + + class Receiver + # Receiver::Private is used to receive private messages, which are addressed to a specific user, encrypted with his + # public key and packed using Salmon::EncryptedSlap + class Private < Receiver + def initialize(recipient_guid, salmon_xml) + super(salmon_xml) + @recipient_guid = recipient_guid + @recipient_private_key = DiasporaFederation.callbacks.trigger(:fetch_private_key_by_user_guid, recipient_guid) + raise RecipientNotFound if @recipient_private_key.nil? + end + + protected + + def slap + @salmon ||= Salmon::EncryptedSlap.from_xml(@salmon_xml, @recipient_private_key) + end + end + end +end diff --git a/lib/diaspora_federation/receiver/public.rb b/lib/diaspora_federation/receiver/public.rb new file mode 100644 index 0000000..8ee22fa --- /dev/null +++ b/lib/diaspora_federation/receiver/public.rb @@ -0,0 +1,13 @@ +module DiasporaFederation + class Receiver + # Receiver::Public is used to receive public messages, which are not addressed to a specific user, unencrypted + # and packed using Salmon::Slap + class Public < Receiver + protected + + def slap + @salmon ||= Salmon::Slap.from_xml(@salmon_xml) + end + end + end +end diff --git a/lib/diaspora_federation/salmon/encrypted_slap.rb b/lib/diaspora_federation/salmon/encrypted_slap.rb index 10cd22e..da45e7f 100644 --- a/lib/diaspora_federation/salmon/encrypted_slap.rb +++ b/lib/diaspora_federation/salmon/encrypted_slap.rb @@ -1,3 +1,5 @@ +require "json" + module DiasporaFederation module Salmon # +EncryptedSlap+ provides class methods for generating and parsing encrypted diff --git a/lib/diaspora_federation/test/factories.rb b/lib/diaspora_federation/test/factories.rb index c54ee50..e332e5f 100644 --- a/lib/diaspora_federation/test/factories.rb +++ b/lib/diaspora_federation/test/factories.rb @@ -1,3 +1,4 @@ +require "diaspora_federation" require "factory_girl" FactoryGirl.define do diff --git a/spec/controllers/diaspora_federation/receive_controller_spec.rb b/spec/controllers/diaspora_federation/receive_controller_spec.rb index 2edc051..d1de84b 100644 --- a/spec/controllers/diaspora_federation/receive_controller_spec.rb +++ b/spec/controllers/diaspora_federation/receive_controller_spec.rb @@ -3,27 +3,44 @@ module DiasporaFederation routes { DiasporaFederation::Engine.routes } describe "POST #public" do - it "succeeds" do - post :public, xml: "" - expect(response).to be_success + it "raises on an empty object" do + expect { post :public, xml: "" }.to raise_error(Salmon::MissingAuthor) end it "returns a 422 if no xml is passed" do post :public expect(response.code).to eq("422") end + + it "returns a 200 if receive! reports no errors" do + expect_any_instance_of(Receiver::Public).to receive(:receive!) + + post :public, xml: "" + expect(response.code).to eq("200") + end end describe "POST #private" do - it "succeeds" do + it "return 404 for because of an unknown guid" do post :private, guid: "any-guid", xml: "" - expect(response).to be_success + expect(response.code).to eq("404") end it "returns a 422 if no xml is passed" do post :private, guid: "any-guid" expect(response.code).to eq("422") end + + it "returns a 200 if receive! reports no errors" do + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with(:fetch_private_key_by_user_guid, "any-guid") + .once + .and_return(true) + expect_any_instance_of(Receiver::Private).to receive(:receive!) + + post :private, guid: "any-guid", xml: "" + expect(response.code).to eq("200") + end end end end diff --git a/spec/lib/diaspora_federation/receiver/private_spec.rb b/spec/lib/diaspora_federation/receiver/private_spec.rb new file mode 100644 index 0000000..ae14252 --- /dev/null +++ b/spec/lib/diaspora_federation/receiver/private_spec.rb @@ -0,0 +1,57 @@ +module DiasporaFederation + describe Receiver::Private do + let(:sender_id) { FactoryGirl.generate(:diaspora_id) } + let(:sender_key) { OpenSSL::PKey::RSA.generate(1024) } + let(:recipient_key) { OpenSSL::PKey::RSA.generate(1024) } + let(:recipient_guid) { FactoryGirl.generate(:guid) } + let(:xml) { + DiasporaFederation::Salmon::EncryptedSlap.generate_xml( + sender_id, + sender_key, + FactoryGirl.build(:request_entity), + recipient_key + ) + } + + it "calls entity_persist if everyting is fine" do + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with(:fetch_public_key_by_diaspora_id, sender_id) + .and_return(sender_key) + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with(:fetch_private_key_by_user_guid, recipient_guid) + .and_return(recipient_key) + + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with(:entity_persist, kind_of(Entity), recipient_guid, sender_id) + + described_class.new(recipient_guid, xml).receive! + end + + it "raises when sender public key is not available" do + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with(:fetch_public_key_by_diaspora_id, sender_id) + .and_return(nil) + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with(:fetch_private_key_by_user_guid, recipient_guid) + .and_return(recipient_key) + + expect { described_class.new(recipient_guid, xml).receive! }.to raise_error SenderNotFound + end + + it "raises when recipient private key is not available" do + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with(:fetch_private_key_by_user_guid, recipient_guid) + .and_return(nil) + + expect { described_class.new(recipient_guid, xml).receive! }.to raise_error RecipientNotFound + end + + it "raises when bad xml was supplied" do + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with(:fetch_private_key_by_user_guid, recipient_guid) + .and_return(recipient_key) + + expect { described_class.new(recipient_guid, "").receive! }.to raise_error Salmon::MissingHeader + end + end +end diff --git a/spec/lib/diaspora_federation/receiver/public_spec.rb b/spec/lib/diaspora_federation/receiver/public_spec.rb new file mode 100644 index 0000000..13eb82e --- /dev/null +++ b/spec/lib/diaspora_federation/receiver/public_spec.rb @@ -0,0 +1,35 @@ +module DiasporaFederation + describe Receiver::Public do + let(:sender_id) { FactoryGirl.generate(:diaspora_id) } + let(:sender_key) { OpenSSL::PKey::RSA.generate(1024) } + let(:xml) { + DiasporaFederation::Salmon::Slap.generate_xml( + sender_id, + sender_key, + FactoryGirl.build(:request_entity) + ) + } + + it "calls entity_persist if everyting is fine" do + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with(:fetch_public_key_by_diaspora_id, sender_id) + .and_return(sender_key) + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with(:entity_persist, kind_of(Entity), nil, sender_id) + + described_class.new(xml).receive! + end + + it "raises when sender public key is not available" do + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with(:fetch_public_key_by_diaspora_id, sender_id) + .and_return(nil) + + expect { described_class.new(xml).receive! }.to raise_error SenderNotFound + end + + it "raises when bad xml was supplied" do + expect { described_class.new("").receive! }.to raise_error Salmon::MissingAuthor + end + end +end diff --git a/test/dummy/config/initializers/diaspora_federation.rb b/test/dummy/config/initializers/diaspora_federation.rb index 95a8ed4..39b315d 100644 --- a/test/dummy/config/initializers/diaspora_federation.rb +++ b/test/dummy/config/initializers/diaspora_federation.rb @@ -86,5 +86,12 @@ DiasporaFederation.configure do |config| on :fetch_entity_author_id_by_guid do nil end + + on :fetch_private_key_by_user_guid do + nil + end + + on :entity_persist do + end end end