Introduce alternative form of the account migration message

In the alternative form author can be the new diaspora user
This commit is contained in:
cmrd Senya 2017-10-30 19:52:54 +02:00
parent 69e523abd0
commit 7e2321d6c9
6 changed files with 231 additions and 96 deletions

View file

@ -8,9 +8,16 @@ This entity is sent when a person changes their diaspora* ID (e.g. when a user m
| Property | Type | Description | | Property | Type | Description |
| ----------- | ---------------------------- | ------------------------------------------------------------------------------------ | | ----------- | ---------------------------- | ------------------------------------------------------------------------------------ |
| `author` | [diaspora\* ID][diaspora-id] | The diaspora\* ID of the closed account. | | `author` | [diaspora\* ID][diaspora-id] | The diaspora\* ID of the sender of the entity. The entity may be sent by either old user identity or new user identity. |
| `person` | [Profile][profile] | New profile of a person | | `person` | [Profile][profile] | New profile of a person. |
| `signature` | [Signature][signature] | Signature that validates original and target diaspora* IDs with the new key of person | | `signature` | [Signature][signature] | Signature that validates original and target diaspora* IDs with the private key of the second identity, other than the entity author. So if the author is the old identity then this signature is made with the new identity key, and vice versa. |
## Optional Properties
| Property | Type | Description |
| ----------- | ---------------------------- | ------------------------------------------------------------------------------------ |
| `old_identity` | [diaspora\* ID][diaspora-id] | The diaspora\* ID of the closed account. This field is mandatory if the author of the entity is the new identity. |
### Signature ### Signature
@ -51,6 +58,7 @@ AccountMigration:old-diaspora-id@example.org:new-diaspora-id@example.com
<signature> <signature>
07b1OIY6sTUQwV5pbpgFK0uz6W4cu+oQnlg410Q4uISUOdNOlBdYqhZJm62VFhgvzt4TZXfiJgoupFkRjP0BsaVaZuP2zKMNvO3ngWOeJRf2oRK4Ub5cEA/g7yijkRc+7y8r1iLJ31MFb1czyeCsLxw9Ol8SvAJddogGiLHDhjE= 07b1OIY6sTUQwV5pbpgFK0uz6W4cu+oQnlg410Q4uISUOdNOlBdYqhZJm62VFhgvzt4TZXfiJgoupFkRjP0BsaVaZuP2zKMNvO3ngWOeJRf2oRK4Ub5cEA/g7yijkRc+7y8r1iLJ31MFb1czyeCsLxw9Ol8SvAJddogGiLHDhjE=
</signature> </signature>
<old_identity>alice@example.org</old_identity>
</account_migration> </account_migration>
~~~ ~~~

View file

@ -8,7 +8,9 @@ module DiasporaFederation
include AccountMigration::Signable include AccountMigration::Signable
# @!attribute [r] author # @!attribute [r] author
# The old diaspora* ID of the person who changes their ID # Sender of the AccountMigration message. Usually it is the old diaspora* ID of the person who changes their ID.
# This property is also allowed to be the new diaspora* ID, which is equal to the author of the included
# profile.
# @see Person#author # @see Person#author
# @return [String] author diaspora* ID # @return [String] author diaspora* ID
property :author, :string property :author, :string
@ -19,14 +21,31 @@ module DiasporaFederation
entity :profile, Entities::Profile entity :profile, Entities::Profile
# @!attribute [r] signature # @!attribute [r] signature
# Signature that validates original and target diaspora* IDs with the new key of person # Signature that validates original and target diaspora* IDs with the private key of the second identity, other
# than the entity author. So if the author is the old identity then this signature is made with the new identity
# key, and vice versa.
# @return [String] signature # @return [String] signature
property :signature, :string, default: nil property :signature, :string, default: nil
def old_user_id # @!attribute [r] old_identity
# Optional attribute which keeps old diaspora* ID. Must be present when author attribute contains new diaspora*
# ID.
# @return [String] old identity
property :old_identity, :string, default: nil
# Returns diaspora* ID of the old person identity.
# @return [String] diaspora* ID of the old person identity
def old_identity
return @old_identity if author_is_new_id?
author author
end end
# Returns diaspora* ID of the new person identity.
# @return [String] diaspora* ID of the new person identity
def new_identity
profile.author
end
# @return [String] string representation of this object # @return [String] string representation of this object
alias to_s unique_migration_descriptor alias to_s unique_migration_descriptor
@ -46,33 +65,33 @@ module DiasporaFederation
private private
def new_user_id def author_is_new_id?
profile.author author == new_identity
end end
def signer_id def signer_id
new_user_id author_is_new_id? ? @old_identity : new_identity
end end
def enriched_properties def enriched_properties
super.tap do |hash| super.tap do |hash|
hash[:signature] = signature || sign_with_new_key hash[:signature] = signature || sign_with_respective_key
end end
end end
# Sign with new user's key # Sign with the key of the #signer_id identity
# @raise [NewPrivateKeyNotFound] if the new user's private key is not found # @raise [PrivateKeyNotFound] if the signer'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_respective_key
privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key, signer_id) privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key, signer_id)
raise NewPrivateKeyNotFound, "signer=#{signer_id} obj=#{self}" if privkey.nil? raise PrivateKeyNotFound, "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 signer=#{signer_id} obj=#{self}" logger.info "event=sign status=complete signature=signature signer=#{signer_id} obj=#{self}"
end end
end end
# Raised, if creating the signature fails, because the new private key of a user was not found # Raised, if creating the signature fails, because the new private key of a user was not found
class NewPrivateKeyNotFound < RuntimeError class PrivateKeyNotFound < RuntimeError
end end
end end
end end

View file

@ -42,6 +42,7 @@ module DiasporaFederation
Fabricator(:account_migration_entity, class_name: DiasporaFederation::Entities::AccountMigration) do Fabricator(:account_migration_entity, class_name: DiasporaFederation::Entities::AccountMigration) do
author { Fabricate.sequence(:diaspora_id) } author { Fabricate.sequence(:diaspora_id) }
profile { Fabricate(:profile_entity) } profile { Fabricate(:profile_entity) }
old_identity { Fabricate.sequence(:diaspora_id) }
end end
Fabricator(:person_entity, class_name: DiasporaFederation::Entities::Person) do Fabricator(:person_entity, class_name: DiasporaFederation::Entities::Person) do

View file

@ -7,6 +7,8 @@ module DiasporaFederation
rule :author, :diaspora_id rule :author, :diaspora_id
rule :profile, :not_nil rule :profile, :not_nil
rule :old_identity, :diaspora_id
end end
end end
end end

View file

@ -1,21 +1,185 @@
module DiasporaFederation module DiasporaFederation
describe Entities::AccountMigration do describe Entities::AccountMigration do
let(:new_diaspora_id) { alice.diaspora_id } let(:old_user) { Fabricate(:user) }
let(:new_author_pkey) { alice.private_key } let(:new_user) { Fabricate(:user) }
let(:hash) { let(:old_diaspora_id) { old_user.diaspora_id }
Fabricate.attributes_for(:account_deletion_entity).merge( let(:new_diaspora_id) { new_user.diaspora_id }
profile: Fabricate(:profile_entity, author: new_diaspora_id)
)
}
let(:data) { let(:data) {
hash.tap {|hash| hash.tap {|hash|
properties = described_class.new(hash).send(:enriched_properties) properties = described_class.new(hash).send(:enriched_properties)
hash[:signature] = properties[:signature] hash[:signature] = properties[:signature]
} }
} }
let(:signature_data) { "AccountMigration:#{hash[:author]}:#{new_diaspora_id}" } let(:signature_data) { "AccountMigration:#{old_diaspora_id}:#{new_diaspora_id}" }
let(:string) { signature_data }
let(:xml) { <<-XML } shared_examples_for "an account migration entity" do
it_behaves_like "an Entity subclass"
it_behaves_like "an XML Entity"
describe "#to_xml" do
it "computes signature when no signature was provided" do
expect_callback(:fetch_private_key, signer_id).and_return(signer_pkey)
entity = Entities::AccountMigration.new(hash)
xml = entity.to_xml
signature = xml.at_xpath("signature").text
expect(verify_signature(signer_pkey, signature, entity.to_s)).to be_truthy
end
it "doesn't change signature if it is already set" do
hash[:signature] = "aa"
xml = Entities::AccountMigration.new(hash).to_xml
expect(xml.at_xpath("signature").text).to eq("aa")
end
it "raises when signature isn't set and key isn't supplied" do
expect_callback(:fetch_private_key, signer_id).and_return(nil)
expect {
Entities::AccountMigration.new(hash).to_xml
}.to raise_error Entities::AccountMigration::PrivateKeyNotFound
end
end
describe "#verify_signature" do
it "doesn't raise anything if correct signature was passed" do
hash[:signature] = sign_with_key(signer_pkey, signature_data)
expect_callback(:fetch_public_key, signer_id).and_return(signer_pkey)
expect { Entities::AccountMigration.new(hash).verify_signature }.not_to raise_error
end
it "raises when no public key for author was fetched" do
expect_callback(:fetch_public_key, anything).and_return(nil)
expect {
Entities::AccountMigration.new(hash).verify_signature
}.to raise_error Entities::AccountMigration::PublicKeyNotFound
end
it "raises when bad author signature was passed" do
hash[:signature] = "abcdef"
expect_callback(:fetch_public_key, signer_id).and_return(signer_pkey.public_key)
expect {
Entities::AccountMigration.new(hash).verify_signature
}.to raise_error Entities::AccountMigration::SignatureVerificationFailed
end
end
describe ".from_hash" do
it "calls #verify_signature" do
expect_any_instance_of(Entities::AccountMigration).to receive(:freeze)
expect_any_instance_of(Entities::AccountMigration).to receive(:verify_signature)
Entities::AccountMigration.from_hash(hash)
end
it "raises when bad author signature was passed" do
hash[:signature] = "abcdef"
expect_callback(:fetch_public_key, signer_id).and_return(signer_pkey.public_key)
expect {
Entities::AccountMigration.from_hash(hash)
}.to raise_error Entities::AccountMigration::SignatureVerificationFailed
end
end
end
context "with old identity as author" do
let(:signer_id) { new_diaspora_id }
let(:signer_pkey) { new_user.private_key }
let(:hash) {
{
author: old_diaspora_id,
profile: Fabricate(:profile_entity, author: new_diaspora_id),
old_identity: old_diaspora_id
}
}
let(:xml) { <<-XML }
<account_migration>
<author>#{data[:author]}</author>
<profile>
<author>#{data[:profile].author}</author>
<first_name>#{data[:profile].first_name}</first_name>
<image_url>#{data[:profile].image_url}</image_url>
<image_url_medium>#{data[:profile].image_url}</image_url_medium>
<image_url_small>#{data[:profile].image_url}</image_url_small>
<bio>#{data[:profile].bio}</bio>
<birthday>#{data[:profile].birthday}</birthday>
<gender>#{data[:profile].gender}</gender>
<location>#{data[:profile].location}</location>
<searchable>#{data[:profile].searchable}</searchable>
<public>#{data[:profile].public}</public>
<nsfw>#{data[:profile].nsfw}</nsfw>
<tag_string>#{data[:profile].tag_string}</tag_string>
</profile>
<signature>#{data[:signature]}</signature>
<old_identity>#{data[:old_identity]}</old_identity>
</account_migration>
XML
it_behaves_like "an account migration entity"
end
context "with new identity as author" do
let(:signer_id) { old_diaspora_id }
let(:signer_pkey) { old_user.private_key }
let(:hash) {
{
author: new_diaspora_id,
profile: Fabricate(:profile_entity, author: new_diaspora_id),
old_identity: old_diaspora_id
}
}
let(:xml) { <<-XML }
<account_migration>
<author>#{data[:author]}</author>
<profile>
<author>#{data[:profile].author}</author>
<first_name>#{data[:profile].first_name}</first_name>
<image_url>#{data[:profile].image_url}</image_url>
<image_url_medium>#{data[:profile].image_url}</image_url_medium>
<image_url_small>#{data[:profile].image_url}</image_url_small>
<bio>#{data[:profile].bio}</bio>
<birthday>#{data[:profile].birthday}</birthday>
<gender>#{data[:profile].gender}</gender>
<location>#{data[:profile].location}</location>
<searchable>#{data[:profile].searchable}</searchable>
<public>#{data[:profile].public}</public>
<nsfw>#{data[:profile].nsfw}</nsfw>
<tag_string>#{data[:profile].tag_string}</tag_string>
</profile>
<signature>#{data[:signature]}</signature>
<old_identity>#{data[:old_identity]}</old_identity>
</account_migration>
XML
it_behaves_like "an account migration entity"
end
context "when author is the new identity and old_identity prop is missing" do
let(:signer_id) { old_diaspora_id }
let(:signer_pkey) { old_user.private_key }
let(:hash) {
{
author: new_diaspora_id,
profile: Fabricate(:profile_entity, author: new_diaspora_id)
}
}
let(:xml) { <<-XML }
<account_migration> <account_migration>
<author>#{data[:author]}</author> <author>#{data[:author]}</author>
<profile> <profile>
@ -37,81 +201,16 @@ module DiasporaFederation
</account_migration> </account_migration>
XML XML
let(:string) { "AccountMigration:#{data[:author]}:#{data[:profile].author}" } it "fails validation on construction" do
it_behaves_like "an Entity subclass"
it_behaves_like "an XML Entity"
describe "#to_xml" do
it "computes signature when no signature was provided" do
expect_callback(:fetch_private_key, new_diaspora_id).and_return(new_author_pkey)
entity = Entities::AccountMigration.new(hash)
xml = entity.to_xml
signature = xml.at_xpath("signature").text
expect(verify_signature(new_author_pkey, signature, entity.to_s)).to be_truthy
end
it "doesn't change signature if it is already set" do
hash[:signature] = "aa"
xml = Entities::AccountMigration.new(hash).to_xml
expect(xml.at_xpath("signature").text).to eq("aa")
end
it "raises when signature isn't set and key isn't supplied" do
expect_callback(:fetch_private_key, new_diaspora_id).and_return(nil)
expect { expect {
Entities::AccountMigration.new(hash).to_xml described_class.new(hash)
}.to raise_error Entities::AccountMigration::NewPrivateKeyNotFound }.to raise_error Entity::ValidationError
end
end
describe "#verify_signature" do
it "doesn't raise anything if correct signatures were passed" do
hash[:signature] = sign_with_key(new_author_pkey, signature_data)
expect_callback(:fetch_public_key, new_diaspora_id).and_return(new_author_pkey)
expect { Entities::AccountMigration.new(hash).verify_signature }.not_to raise_error
end end
it "raises when no public key for author was fetched" do it "fails validation on parsing" do
expect_callback(:fetch_public_key, anything).and_return(nil)
expect { expect {
Entities::AccountMigration.new(hash).verify_signature DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML(xml).root)
}.to raise_error Entities::AccountMigration::PublicKeyNotFound }.to raise_error Entity::ValidationError
end
it "raises when bad author signature was passed" do
hash[:signature] = "abcdef"
expect_callback(:fetch_public_key, new_diaspora_id).and_return(new_author_pkey.public_key)
expect {
Entities::AccountMigration.new(hash).verify_signature
}.to raise_error Entities::AccountMigration::SignatureVerificationFailed
end
end
describe ".from_hash" do
it "calls #verify_signature" do
expect_any_instance_of(Entities::AccountMigration).to receive(:freeze)
expect_any_instance_of(Entities::AccountMigration).to receive(:verify_signature)
Entities::AccountMigration.from_hash(hash)
end
it "raises when bad author signature was passed" do
hash[:signature] = "abcdef"
expect_callback(:fetch_public_key, new_diaspora_id).and_return(new_author_pkey.public_key)
expect {
Entities::AccountMigration.from_hash(hash)
}.to raise_error Entities::AccountMigration::SignatureVerificationFailed
end end
end end
end end

View file

@ -8,12 +8,18 @@ module DiasporaFederation
let(:property) { :author } let(:property) { :author }
end end
describe "#person" do describe "#profile" do
it_behaves_like "a property with a value validation/restriction" do it_behaves_like "a property with a value validation/restriction" do
let(:property) { :profile } let(:property) { :profile }
let(:wrong_values) { [nil] } let(:wrong_values) { [nil] }
let(:correct_values) { [] } let(:correct_values) { [] }
end end
end end
describe "#old_identity" do
it_behaves_like "a diaspora* ID validator" do
let(:property) { :old_identity }
end
end
end end
end end