diff --git a/lib/diaspora_federation/entities.rb b/lib/diaspora_federation/entities.rb index 5ae794b..66bb1aa 100644 --- a/lib/diaspora_federation/entities.rb +++ b/lib/diaspora_federation/entities.rb @@ -13,6 +13,7 @@ require "diaspora_federation/entities/related_entity" # abstract types require "diaspora_federation/entities/post" require "diaspora_federation/entities/signable" +require "diaspora_federation/entities/account_migration/signable" require "diaspora_federation/entities/relayable" # types diff --git a/lib/diaspora_federation/entities/account_migration.rb b/lib/diaspora_federation/entities/account_migration.rb index 8085bc4..6fd8d07 100644 --- a/lib/diaspora_federation/entities/account_migration.rb +++ b/lib/diaspora_federation/entities/account_migration.rb @@ -5,7 +5,7 @@ module DiasporaFederation # # @see Validators::AccountMigrationValidator class AccountMigration < Entity - include Signable + include AccountMigration::Signable # @!attribute [r] author # The old diaspora* ID of the person who changes their ID @@ -23,16 +23,18 @@ module DiasporaFederation # @return [String] signature property :signature, :string, default: nil - # @return [String] string representation of this object - def to_s - "AccountMigration:#{author}:#{profile.author}" + def old_user_id + author end + # @return [String] string representation of this object + alias to_s unique_migration_descriptor + # Shortcut for calling super method with sensible arguments # # @see DiasporaFederation::Entities::Signable#verify_signature def verify_signature - super(profile.author, :signature) + super(signer_id, :signature) end # Calls super and additionally does signature verification for the instantiated entity. @@ -44,9 +46,12 @@ module DiasporaFederation private - # @see DiasporaFederation::Entities::Signable#signature_data - def signature_data - to_s + def new_user_id + profile.author + end + + def signer_id + new_user_id end def enriched_properties @@ -59,10 +64,10 @@ module DiasporaFederation # @raise [NewPrivateKeyNotFound] if the new user's private key is not found # @return [String] A Base64 encoded signature of #signature_data with key def sign_with_new_key - privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key, profile.author) - raise NewPrivateKeyNotFound, "author=#{profile.author} obj=#{self}" if privkey.nil? + privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key, signer_id) + raise NewPrivateKeyNotFound, "signer=#{signer_id} obj=#{self}" if privkey.nil? sign_with_key(privkey).tap do - logger.info "event=sign status=complete signature=signature author=#{profile.author} obj=#{self}" + logger.info "event=sign status=complete signature=signature signer=#{signer_id} obj=#{self}" end end diff --git a/lib/diaspora_federation/entities/account_migration/signable.rb b/lib/diaspora_federation/entities/account_migration/signable.rb new file mode 100644 index 0000000..40cb5f1 --- /dev/null +++ b/lib/diaspora_federation/entities/account_migration/signable.rb @@ -0,0 +1,24 @@ +module DiasporaFederation + module Entities + class AccountMigration < Entity + # AccountMigration::Signable is a module that encapsulates basic signature generation/verification flow for + # AccountMigration entity. + # + # It is possible that implementation of diaspora* protocol requires to compute the signature for the + # AccountMigration entity without instantiating the entity. In this case this module may be useful. + module Signable + include Entities::Signable + + # @return [String] string which is uniquely represents migration occasion + def unique_migration_descriptor + "AccountMigration:#{old_identity}:#{new_identity}" + end + + # @see DiasporaFederation::Entities::Signable#signature_data + def signature_data + unique_migration_descriptor + end + end + end + end +end diff --git a/spec/lib/diaspora_federation/entities/account_migration/signable_spec.rb b/spec/lib/diaspora_federation/entities/account_migration/signable_spec.rb new file mode 100644 index 0000000..b79688b --- /dev/null +++ b/spec/lib/diaspora_federation/entities/account_migration/signable_spec.rb @@ -0,0 +1,39 @@ +module DiasporaFederation + describe Entities::AccountMigration::Signable do + let(:entity) { TestAMSignableEntity.new({}) } + + class TestAMSignableEntity < Entity + include Entities::AccountMigration::Signable + + property :my_signature, :string, default: nil + + def old_identity + "old" + end + + def new_identity + "new" + end + + def freeze; end + end + + it_behaves_like "a signable" do + let(:test_class) { TestAMSignableEntity } + let(:test_string) { "AccountMigration:old:new" } + end + + describe "#unique_migration_descriptor" do + it "composes a string using #old_identity and #new_identity" do + expect(entity.unique_migration_descriptor).to eq("AccountMigration:old:new") + end + end + + describe "#signature_data" do + it "delegates to #unique_migration_descriptor" do + expect(entity).to receive(:unique_migration_descriptor).and_return("test123") + expect(entity.signature_data).to eq("test123") + end + end + end +end diff --git a/spec/lib/diaspora_federation/entities/signable_spec.rb b/spec/lib/diaspora_federation/entities/signable_spec.rb index bcb31c3..6ae9e7f 100644 --- a/spec/lib/diaspora_federation/entities/signable_spec.rb +++ b/spec/lib/diaspora_federation/entities/signable_spec.rb @@ -1,9 +1,6 @@ module DiasporaFederation describe Entities::Signable do TEST_STRING_VALUE = "abc123".freeze - let(:private_key) { OpenSSL::PKey::RSA.generate(1024) } - let(:test_string) { TEST_STRING_VALUE } - let(:test_signature) { sign_with_key(private_key, test_string) } class TestSignableEntity < Entity include Entities::Signable @@ -27,51 +24,9 @@ module DiasporaFederation end end - describe "#sign_with_key" do - it "produces a correct signature" do - signature = TestSignableEntity.new({}).sign_with_key(private_key) - expect(verify_signature(private_key.public_key, signature, test_string)).to be_truthy - end - end - - describe "#verify_signature" do - it "doesn't raise if signature is correct" do - expect_callback(:fetch_public_key, "id@example.tld").and_return(private_key.public_key) - - expect { - TestSignableEntity - .new(my_signature: test_signature) - .verify_signature("id@example.tld", :my_signature) - }.not_to raise_error - end - - it "raises PublicKeyNotFound when key isn't provided" do - expect_callback(:fetch_public_key, "id@example.tld").and_return(nil) - - expect { - TestSignableEntity - .new(my_signature: test_signature) - .verify_signature("id@example.tld", :my_signature) - }.to raise_error(Entities::Signable::PublicKeyNotFound) - end - - it "raises SignatureVerificationFailed when signature isn't provided" do - expect_callback(:fetch_public_key, "id@example.tld").and_return(private_key.public_key) - - expect { - TestSignableEntity.new({}).verify_signature("id@example.tld", :my_signature) - }.to raise_error(Entities::Signable::SignatureVerificationFailed) - end - - it "raises SignatureVerificationFailed when signature is wrong" do - expect_callback(:fetch_public_key, "id@example.tld").and_return(private_key.public_key) - - expect { - TestSignableEntity - .new(my_signature: "faked signature") - .verify_signature("id@example.tld", :my_signature) - }.to raise_error(Entities::Signable::SignatureVerificationFailed) - end + it_behaves_like "a signable" do + let(:test_class) { TestSignableEntity } + let(:test_string) { TEST_STRING_VALUE } end end end diff --git a/spec/support/shared_signable_spec.rb b/spec/support/shared_signable_spec.rb new file mode 100644 index 0000000..8e5140d --- /dev/null +++ b/spec/support/shared_signable_spec.rb @@ -0,0 +1,51 @@ +shared_examples "a signable" do + let(:private_key) { OpenSSL::PKey::RSA.generate(1024) } + let(:test_signature) { sign_with_key(private_key, test_string) } + + describe "#sign_with_key" do + it "produces a correct signature" do + signature = test_class.new({}).sign_with_key(private_key) + expect(verify_signature(private_key.public_key, signature, test_string)).to be_truthy + end + end + + describe "#verify_signature" do + it "doesn't raise if signature is correct" do + expect_callback(:fetch_public_key, "id@example.tld").and_return(private_key.public_key) + + expect { + test_class + .new(my_signature: test_signature) + .verify_signature("id@example.tld", :my_signature) + }.not_to raise_error + end + + it "raises PublicKeyNotFound when key isn't provided" do + expect_callback(:fetch_public_key, "id@example.tld").and_return(nil) + + expect { + test_class + .new(my_signature: test_signature) + .verify_signature("id@example.tld", :my_signature) + }.to raise_error(DiasporaFederation::Entities::Signable::PublicKeyNotFound) + end + + it "raises SignatureVerificationFailed when signature isn't provided" do + expect_callback(:fetch_public_key, "id@example.tld").and_return(private_key.public_key) + + expect { + test_class.new({}).verify_signature("id@example.tld", :my_signature) + }.to raise_error(DiasporaFederation::Entities::Signable::SignatureVerificationFailed) + end + + it "raises SignatureVerificationFailed when signature is wrong" do + expect_callback(:fetch_public_key, "id@example.tld").and_return(private_key.public_key) + + expect { + test_class + .new(my_signature: "faked signature") + .verify_signature("id@example.tld", :my_signature) + }.to raise_error(DiasporaFederation::Entities::Signable::SignatureVerificationFailed) + end + end +end