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
|
# abstract types
|
||||||
require "diaspora_federation/entities/post"
|
require "diaspora_federation/entities/post"
|
||||||
require "diaspora_federation/entities/signable"
|
require "diaspora_federation/entities/signable"
|
||||||
|
require "diaspora_federation/entities/account_migration/signable"
|
||||||
require "diaspora_federation/entities/relayable"
|
require "diaspora_federation/entities/relayable"
|
||||||
|
|
||||||
# types
|
# types
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ module DiasporaFederation
|
||||||
#
|
#
|
||||||
# @see Validators::AccountMigrationValidator
|
# @see Validators::AccountMigrationValidator
|
||||||
class AccountMigration < Entity
|
class AccountMigration < Entity
|
||||||
include Signable
|
include AccountMigration::Signable
|
||||||
|
|
||||||
# @!attribute [r] author
|
# @!attribute [r] author
|
||||||
# The old diaspora* ID of the person who changes their ID
|
# The old diaspora* ID of the person who changes their ID
|
||||||
|
|
@ -23,16 +23,18 @@ module DiasporaFederation
|
||||||
# @return [String] signature
|
# @return [String] signature
|
||||||
property :signature, :string, default: nil
|
property :signature, :string, default: nil
|
||||||
|
|
||||||
# @return [String] string representation of this object
|
def old_user_id
|
||||||
def to_s
|
author
|
||||||
"AccountMigration:#{author}:#{profile.author}"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [String] string representation of this object
|
||||||
|
alias to_s unique_migration_descriptor
|
||||||
|
|
||||||
# Shortcut for calling super method with sensible arguments
|
# Shortcut for calling super method with sensible arguments
|
||||||
#
|
#
|
||||||
# @see DiasporaFederation::Entities::Signable#verify_signature
|
# @see DiasporaFederation::Entities::Signable#verify_signature
|
||||||
def verify_signature
|
def verify_signature
|
||||||
super(profile.author, :signature)
|
super(signer_id, :signature)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Calls super and additionally does signature verification for the instantiated entity.
|
# Calls super and additionally does signature verification for the instantiated entity.
|
||||||
|
|
@ -44,9 +46,12 @@ module DiasporaFederation
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# @see DiasporaFederation::Entities::Signable#signature_data
|
def new_user_id
|
||||||
def signature_data
|
profile.author
|
||||||
to_s
|
end
|
||||||
|
|
||||||
|
def signer_id
|
||||||
|
new_user_id
|
||||||
end
|
end
|
||||||
|
|
||||||
def enriched_properties
|
def enriched_properties
|
||||||
|
|
@ -59,10 +64,10 @@ module DiasporaFederation
|
||||||
# @raise [NewPrivateKeyNotFound] if the new user's private key is not found
|
# @raise [NewPrivateKeyNotFound] if the new user's private key is not found
|
||||||
# @return [String] A Base64 encoded signature of #signature_data with key
|
# @return [String] A Base64 encoded signature of #signature_data with key
|
||||||
def sign_with_new_key
|
def sign_with_new_key
|
||||||
privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key, profile.author)
|
privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key, signer_id)
|
||||||
raise NewPrivateKeyNotFound, "author=#{profile.author} obj=#{self}" if privkey.nil?
|
raise NewPrivateKeyNotFound, "signer=#{signer_id} obj=#{self}" if privkey.nil?
|
||||||
sign_with_key(privkey).tap do
|
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
|
||||||
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
|
module DiasporaFederation
|
||||||
describe Entities::Signable do
|
describe Entities::Signable do
|
||||||
TEST_STRING_VALUE = "abc123".freeze
|
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
|
class TestSignableEntity < Entity
|
||||||
include Entities::Signable
|
include Entities::Signable
|
||||||
|
|
@ -27,51 +24,9 @@ module DiasporaFederation
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#sign_with_key" do
|
it_behaves_like "a signable" do
|
||||||
it "produces a correct signature" do
|
let(:test_class) { TestSignableEntity }
|
||||||
signature = TestSignableEntity.new({}).sign_with_key(private_key)
|
let(:test_string) { TEST_STRING_VALUE }
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end
|
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