Extract account migration sign feature to a module
This module can be used to compute account migration signature in other objects besides Entities::AccountMigration. For instance this is to be used in AccountMigration model of diaspora* web application.
This commit is contained in:
parent
3cffc9d1d2
commit
69e523abd0
6 changed files with 134 additions and 59 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
51
spec/support/shared_signable_spec.rb
Normal file
51
spec/support/shared_signable_spec.rb
Normal file
|
|
@ -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
|
||||
Loading…
Reference in a new issue