Merge pull request #54 from cmrd-senya/account_rename
AccountMigration entity
This commit is contained in:
commit
1fed7b501c
13 changed files with 436 additions and 47 deletions
59
docs/_entities/account_migration.md
Normal file
59
docs/_entities/account_migration.md
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
---
|
||||||
|
title: AccountMigration
|
||||||
|
---
|
||||||
|
|
||||||
|
This entity is sent when a person changes their diaspora* ID (e.g. when a user migration from one to another pod happens).
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description |
|
||||||
|
| ----------- | ---------------------------- | ------------------------------------------------------------------------------------ |
|
||||||
|
| `author` | [diaspora\* ID][diaspora-id] | The diaspora\* ID of the closed account. |
|
||||||
|
| `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
|
||||||
|
|
||||||
|
The signature base string is produced by concatenating the following substrings together, separated by semicolon (`:`):
|
||||||
|
|
||||||
|
1) The entity name specifier: `AccountMigration`.
|
||||||
|
|
||||||
|
2) diaspora\* ID of the closed account (old diaspora\* ID).
|
||||||
|
|
||||||
|
3) diaspora\* ID of the replacement account (new diaspora\* ID).
|
||||||
|
|
||||||
|
Example of a string:
|
||||||
|
|
||||||
|
~~~
|
||||||
|
AccountMigration:old-diaspora-id@example.org:new-diaspora-id@example.com
|
||||||
|
~~~
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
~~~xml
|
||||||
|
<account_migration>
|
||||||
|
<author>alice@example.org</author>
|
||||||
|
<profile>
|
||||||
|
<author>alice@newpod.example.net</author>
|
||||||
|
<first_name>my name</first_name>
|
||||||
|
<last_name/>
|
||||||
|
<image_url>/assets/user/default.png</image_url>
|
||||||
|
<image_url_medium>/assets/user/default.png</image_url_medium>
|
||||||
|
<image_url_small>/assets/user/default.png</image_url_small>
|
||||||
|
<birthday>1988-07-15</birthday>
|
||||||
|
<gender>Female</gender>
|
||||||
|
<bio>some text about me</bio>
|
||||||
|
<location>github</location>
|
||||||
|
<searchable>true</searchable>
|
||||||
|
<nsfw>false</nsfw>
|
||||||
|
<tag_string>#i #love #tags</tag_string>
|
||||||
|
</profile>
|
||||||
|
<signature>
|
||||||
|
07b1OIY6sTUQwV5pbpgFK0uz6W4cu+oQnlg410Q4uISUOdNOlBdYqhZJm62VFhgvzt4TZXfiJgoupFkRjP0BsaVaZuP2zKMNvO3ngWOeJRf2oRK4Ub5cEA/g7yijkRc+7y8r1iLJ31MFb1czyeCsLxw9Ol8SvAJddogGiLHDhjE=
|
||||||
|
</signature>
|
||||||
|
</account_migration>
|
||||||
|
~~~
|
||||||
|
|
||||||
|
[diaspora-id]: {{ site.baseurl }}/federation/types.html#diaspora-id
|
||||||
|
[profile]: {{ site.baseurl }}/entities/profile.html
|
||||||
|
[signature]: {{ site.baseurl }}/federation/types.html#signature
|
||||||
|
|
@ -12,6 +12,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/relayable"
|
require "diaspora_federation/entities/relayable"
|
||||||
|
|
||||||
# types
|
# types
|
||||||
|
|
@ -19,6 +20,7 @@ require "diaspora_federation/entities/profile"
|
||||||
require "diaspora_federation/entities/person"
|
require "diaspora_federation/entities/person"
|
||||||
require "diaspora_federation/entities/contact"
|
require "diaspora_federation/entities/contact"
|
||||||
require "diaspora_federation/entities/account_deletion"
|
require "diaspora_federation/entities/account_deletion"
|
||||||
|
require "diaspora_federation/entities/account_migration"
|
||||||
|
|
||||||
require "diaspora_federation/entities/participation"
|
require "diaspora_federation/entities/participation"
|
||||||
require "diaspora_federation/entities/like"
|
require "diaspora_federation/entities/like"
|
||||||
|
|
|
||||||
74
lib/diaspora_federation/entities/account_migration.rb
Normal file
74
lib/diaspora_federation/entities/account_migration.rb
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
module DiasporaFederation
|
||||||
|
module Entities
|
||||||
|
# This entity is sent when a person changes their diaspora* ID (e.g. when a user migration
|
||||||
|
# from one to another pod happens).
|
||||||
|
#
|
||||||
|
# @see Validators::AccountMigrationValidator
|
||||||
|
class AccountMigration < Entity
|
||||||
|
include Signable
|
||||||
|
|
||||||
|
# @!attribute [r] author
|
||||||
|
# The old diaspora* ID of the person who changes their ID
|
||||||
|
# @see Person#author
|
||||||
|
# @return [String] author diaspora* ID
|
||||||
|
property :author, :string
|
||||||
|
|
||||||
|
# @!attribute [r] profile
|
||||||
|
# Holds new updated profile of a person, including diaspora* ID
|
||||||
|
# @return [Person] person new data
|
||||||
|
entity :profile, Entities::Profile
|
||||||
|
|
||||||
|
# @!attribute [r] signature
|
||||||
|
# Signature that validates original and target diaspora* IDs with the new key of person
|
||||||
|
# @return [String] signature
|
||||||
|
property :signature, :string, default: nil
|
||||||
|
|
||||||
|
# @return [String] string representation of this object
|
||||||
|
def to_s
|
||||||
|
"AccountMigration:#{author}:#{profile.author}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Shortcut for calling super method with sensible arguments
|
||||||
|
#
|
||||||
|
# @see DiasporaFederation::Entities::Signable#verify_signature
|
||||||
|
def verify_signature
|
||||||
|
super(profile.author, :signature)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Calls super and additionally does signature verification for the instantiated entity.
|
||||||
|
#
|
||||||
|
# @see DiasporaFederation::Entity.from_hash
|
||||||
|
def self.from_hash(*args)
|
||||||
|
super.tap(&:verify_signature)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# @see DiasporaFederation::Entities::Signable#signature_data
|
||||||
|
def signature_data
|
||||||
|
to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def enriched_properties
|
||||||
|
super.tap do |hash|
|
||||||
|
hash[:signature] = signature || sign_with_new_key
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Sign with new user's key
|
||||||
|
# @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?
|
||||||
|
sign_with_key(privkey).tap do
|
||||||
|
logger.info "event=sign status=complete signature=signature author=#{profile.author} obj=#{self}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Raised, if creating the signature fails, because the new private key of a user was not found
|
||||||
|
class NewPrivateKeyNotFound < RuntimeError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -5,10 +5,7 @@ module DiasporaFederation
|
||||||
# has a parent, identified by guid. Relayables are also signed and signing/verification
|
# has a parent, identified by guid. Relayables are also signed and signing/verification
|
||||||
# logic is embedded into Salmon XML processing code.
|
# logic is embedded into Salmon XML processing code.
|
||||||
module Relayable
|
module Relayable
|
||||||
include Logging
|
include Signable
|
||||||
|
|
||||||
# Digest instance used for signing
|
|
||||||
DIGEST = OpenSSL::Digest::SHA256.new
|
|
||||||
|
|
||||||
# Order from the parsed xml for signature
|
# Order from the parsed xml for signature
|
||||||
# @return [Array] order from xml
|
# @return [Array] order from xml
|
||||||
|
|
@ -79,7 +76,8 @@ module DiasporaFederation
|
||||||
end
|
end
|
||||||
|
|
||||||
# Verifies the signatures (+author_signature+ and +parent_author_signature+ if needed).
|
# Verifies the signatures (+author_signature+ and +parent_author_signature+ if needed).
|
||||||
# @raise [SignatureVerificationFailed] if the signature is not valid or no public key is found
|
# @raise [SignatureVerificationFailed] if the signature is not valid
|
||||||
|
# @raise [PublicKeyNotFound] if no public key is found
|
||||||
def verify_signatures
|
def verify_signatures
|
||||||
verify_signature(author, :author_signature)
|
verify_signature(author, :author_signature)
|
||||||
|
|
||||||
|
|
@ -107,24 +105,6 @@ module DiasporaFederation
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Check that signature is a correct signature
|
|
||||||
#
|
|
||||||
# @param [String] author The author of the signature
|
|
||||||
# @param [String] signature_key The signature to be verified
|
|
||||||
# @return [Boolean] signature valid
|
|
||||||
def verify_signature(author, signature_key)
|
|
||||||
pubkey = DiasporaFederation.callbacks.trigger(:fetch_public_key, author)
|
|
||||||
raise PublicKeyNotFound, "signature=#{signature_key} person=#{author} obj=#{self}" if pubkey.nil?
|
|
||||||
|
|
||||||
signature = public_send(signature_key)
|
|
||||||
raise SignatureVerificationFailed, "no #{signature_key} for #{self}" if signature.nil?
|
|
||||||
|
|
||||||
valid = pubkey.verify(DIGEST, Base64.decode64(signature), signature_data)
|
|
||||||
raise SignatureVerificationFailed, "wrong #{signature_key} for #{self}" unless valid
|
|
||||||
|
|
||||||
logger.info "event=verify_signature signature=#{signature_key} status=valid obj=#{self}"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Sign with author key
|
# Sign with author key
|
||||||
# @raise [AuthorPrivateKeyNotFound] if the author private key is not found
|
# @raise [AuthorPrivateKeyNotFound] if the author 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
|
||||||
|
|
@ -147,14 +127,6 @@ module DiasporaFederation
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sign the data with the key
|
|
||||||
#
|
|
||||||
# @param [OpenSSL::PKey::RSA] privkey An RSA key
|
|
||||||
# @return [String] A Base64 encoded signature of #signature_data with key
|
|
||||||
def sign_with_key(privkey)
|
|
||||||
Base64.strict_encode64(privkey.sign(DIGEST, signature_data))
|
|
||||||
end
|
|
||||||
|
|
||||||
# Update the signatures with the keys of the author and the parent
|
# Update the signatures with the keys of the author and the parent
|
||||||
# if the signatures are not there yet and if the keys are available.
|
# if the signatures are not there yet and if the keys are available.
|
||||||
#
|
#
|
||||||
|
|
@ -245,14 +217,6 @@ module DiasporaFederation
|
||||||
# Raised, if creating the author_signature fails, because the private key was not found
|
# Raised, if creating the author_signature fails, because the private key was not found
|
||||||
class AuthorPrivateKeyNotFound < RuntimeError
|
class AuthorPrivateKeyNotFound < RuntimeError
|
||||||
end
|
end
|
||||||
|
|
||||||
# Raised, if verify_signatures fails to verify signatures (no public key found)
|
|
||||||
class PublicKeyNotFound < RuntimeError
|
|
||||||
end
|
|
||||||
|
|
||||||
# Raised, if verify_signatures fails to verify signatures (signatures are wrong)
|
|
||||||
class SignatureVerificationFailed < RuntimeError
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
54
lib/diaspora_federation/entities/signable.rb
Normal file
54
lib/diaspora_federation/entities/signable.rb
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
module DiasporaFederation
|
||||||
|
module Entities
|
||||||
|
# Signable is a module that encapsulates basic signature generation/verification flow for entities.
|
||||||
|
module Signable
|
||||||
|
include Logging
|
||||||
|
|
||||||
|
# Digest instance used for signing
|
||||||
|
DIGEST = OpenSSL::Digest::SHA256.new
|
||||||
|
|
||||||
|
# Sign the data with the key
|
||||||
|
#
|
||||||
|
# @param [OpenSSL::PKey::RSA] privkey An RSA key
|
||||||
|
# @return [String] A Base64 encoded signature of #signature_data with key
|
||||||
|
def sign_with_key(privkey)
|
||||||
|
Base64.strict_encode64(privkey.sign(DIGEST, signature_data))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check that signature is a correct signature
|
||||||
|
#
|
||||||
|
# @param [String] author The author of the signature
|
||||||
|
# @param [String] signature_key The signature to be verified
|
||||||
|
# @raise [SignatureVerificationFailed] if the signature is not valid
|
||||||
|
# @raise [PublicKeyNotFound] if no public key is found
|
||||||
|
def verify_signature(author, signature_key)
|
||||||
|
pubkey = DiasporaFederation.callbacks.trigger(:fetch_public_key, author)
|
||||||
|
raise PublicKeyNotFound, "signature=#{signature_key} person=#{author} obj=#{self}" if pubkey.nil?
|
||||||
|
|
||||||
|
signature = public_send(signature_key)
|
||||||
|
raise SignatureVerificationFailed, "no #{signature_key} for #{self}" if signature.nil?
|
||||||
|
|
||||||
|
valid = pubkey.verify(DIGEST, Base64.decode64(signature), signature_data)
|
||||||
|
raise SignatureVerificationFailed, "wrong #{signature_key} for #{self}" unless valid
|
||||||
|
|
||||||
|
logger.info "event=verify_signature signature=#{signature_key} status=valid obj=#{self}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# This method defines what data is used for a signature creation/verification
|
||||||
|
#
|
||||||
|
# @abstract
|
||||||
|
# @return [String] a string to sign
|
||||||
|
def signature_data
|
||||||
|
raise NotImplementedError.new("you must override this method to define signature base string")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Raised, if verify_signatures fails to verify signatures (no public key found)
|
||||||
|
class PublicKeyNotFound < RuntimeError
|
||||||
|
end
|
||||||
|
|
||||||
|
# Raised, if verify_signatures fails to verify signatures (signatures are wrong)
|
||||||
|
class SignatureVerificationFailed < RuntimeError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -41,6 +41,13 @@ module DiasporaFederation
|
||||||
searchable true
|
searchable true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
factory :account_migration_entity, class: DiasporaFederation::Entities::AccountMigration do
|
||||||
|
author { generate(:diaspora_id) }
|
||||||
|
profile {
|
||||||
|
FactoryGirl.build(:profile_entity)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
factory :person_entity, class: DiasporaFederation::Entities::Person do
|
factory :person_entity, class: DiasporaFederation::Entities::Person do
|
||||||
guid
|
guid
|
||||||
author { generate(:diaspora_id) }
|
author { generate(:diaspora_id) }
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ require "diaspora_federation/validators/relayable_validator"
|
||||||
|
|
||||||
# types
|
# types
|
||||||
require "diaspora_federation/validators/account_deletion_validator"
|
require "diaspora_federation/validators/account_deletion_validator"
|
||||||
|
require "diaspora_federation/validators/account_migration_validator"
|
||||||
require "diaspora_federation/validators/comment_validator"
|
require "diaspora_federation/validators/comment_validator"
|
||||||
require "diaspora_federation/validators/contact_validator"
|
require "diaspora_federation/validators/contact_validator"
|
||||||
require "diaspora_federation/validators/conversation_validator"
|
require "diaspora_federation/validators/conversation_validator"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
module DiasporaFederation
|
||||||
|
module Validators
|
||||||
|
# This validates a {Entities::AccountMigration}.
|
||||||
|
class AccountMigrationValidator < Validation::Validator
|
||||||
|
include Validation
|
||||||
|
|
||||||
|
rule :author, %i(not_empty diaspora_id)
|
||||||
|
|
||||||
|
rule :profile, :not_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
119
spec/lib/diaspora_federation/entities/account_migration_spec.rb
Normal file
119
spec/lib/diaspora_federation/entities/account_migration_spec.rb
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
module DiasporaFederation
|
||||||
|
describe Entities::AccountMigration do
|
||||||
|
let(:new_diaspora_id) { alice.diaspora_id }
|
||||||
|
let(:new_author_pkey) { alice.private_key }
|
||||||
|
let(:hash) {
|
||||||
|
FactoryGirl.attributes_for(
|
||||||
|
:account_migration_entity,
|
||||||
|
profile: FactoryGirl.build(:profile_entity, author: new_diaspora_id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let(:data) {
|
||||||
|
hash.tap {|hash|
|
||||||
|
properties = described_class.new(hash).send(:enriched_properties)
|
||||||
|
hash[:signature] = properties[:signature]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let(:signature_data) { "AccountMigration:#{hash[:author]}:#{new_diaspora_id}" }
|
||||||
|
|
||||||
|
let(:xml) { <<-XML }
|
||||||
|
<account_migration>
|
||||||
|
<author>#{data[:author]}</author>
|
||||||
|
<profile>
|
||||||
|
<diaspora_handle>#{data[:profile].author}</diaspora_handle>
|
||||||
|
<first_name>#{data[:profile].first_name}</first_name>
|
||||||
|
<last_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>
|
||||||
|
<birthday>#{data[:profile].birthday}</birthday>
|
||||||
|
<gender>#{data[:profile].gender}</gender>
|
||||||
|
<bio>#{data[:profile].bio}</bio>
|
||||||
|
<location>#{data[:profile].location}</location>
|
||||||
|
<searchable>#{data[:profile].searchable}</searchable>
|
||||||
|
<nsfw>#{data[:profile].nsfw}</nsfw>
|
||||||
|
<tag_string>#{data[:profile].tag_string}</tag_string>
|
||||||
|
</profile>
|
||||||
|
<signature>#{data[:signature]}</signature>
|
||||||
|
</account_migration>
|
||||||
|
XML
|
||||||
|
|
||||||
|
let(:string) { "AccountMigration:#{data[:author]}:#{data[:profile].author}" }
|
||||||
|
|
||||||
|
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 {
|
||||||
|
Entities::AccountMigration.new(hash).to_xml
|
||||||
|
}.to raise_error Entities::AccountMigration::NewPrivateKeyNotFound
|
||||||
|
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
|
||||||
|
|
||||||
|
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, 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
|
||||||
|
|
@ -15,14 +15,6 @@ module DiasporaFederation
|
||||||
|
|
||||||
let(:legacy_signature_data) { "#{guid};#{author};#{property};#{parent_guid}" }
|
let(:legacy_signature_data) { "#{guid};#{author};#{property};#{parent_guid}" }
|
||||||
|
|
||||||
def sign_with_key(privkey, signature_data)
|
|
||||||
Base64.strict_encode64(privkey.sign(OpenSSL::Digest::SHA256.new, signature_data))
|
|
||||||
end
|
|
||||||
|
|
||||||
def verify_signature(pubkey, signature, signed_string)
|
|
||||||
pubkey.verify(OpenSSL::Digest::SHA256.new, Base64.decode64(signature), signed_string)
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "#initialize" do
|
describe "#initialize" do
|
||||||
it "filters signatures from order" do
|
it "filters signatures from order" do
|
||||||
xml_order = [:author, :guid, :parent_guid, :property, "new_property", :author_signature]
|
xml_order = [:author, :guid, :parent_guid, :property, "new_property", :author_signature]
|
||||||
|
|
|
||||||
77
spec/lib/diaspora_federation/entities/signable_spec.rb
Normal file
77
spec/lib/diaspora_federation/entities/signable_spec.rb
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
property :my_signature, :string, default: nil
|
||||||
|
|
||||||
|
def signature_data
|
||||||
|
TEST_STRING_VALUE
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#signature_data" do
|
||||||
|
it "raises NotImplementedError when not overridden" do
|
||||||
|
class TestEntity < Entity
|
||||||
|
include Entities::Signable
|
||||||
|
end
|
||||||
|
|
||||||
|
expect {
|
||||||
|
TestEntity.new({}).signature_data
|
||||||
|
}.to raise_error(NotImplementedError)
|
||||||
|
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
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
module DiasporaFederation
|
||||||
|
describe Validators::AccountMigrationValidator do
|
||||||
|
let(:entity) { :account_migration_entity }
|
||||||
|
|
||||||
|
it_behaves_like "a common validator"
|
||||||
|
|
||||||
|
it_behaves_like "a diaspora* ID validator" do
|
||||||
|
let(:property) { :author }
|
||||||
|
let(:mandatory) { true }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#person" do
|
||||||
|
it_behaves_like "a property with a value validation/restriction" do
|
||||||
|
let(:property) { :profile }
|
||||||
|
let(:wrong_values) { [nil] }
|
||||||
|
let(:correct_values) { [] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -45,6 +45,14 @@ def add_signatures(hash, klass=described_class)
|
||||||
hash[:parent_author_signature] = properties[:parent_author_signature]
|
hash[:parent_author_signature] = properties[:parent_author_signature]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def sign_with_key(privkey, signature_data)
|
||||||
|
Base64.strict_encode64(privkey.sign(OpenSSL::Digest::SHA256.new, signature_data))
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify_signature(pubkey, signature, signed_string)
|
||||||
|
pubkey.verify(OpenSSL::Digest::SHA256.new, Base64.decode64(signature), signed_string)
|
||||||
|
end
|
||||||
|
|
||||||
# Requires supporting files with custom matchers and macros, etc,
|
# Requires supporting files with custom matchers and macros, etc,
|
||||||
# in ./support/ and its subdirectories.
|
# in ./support/ and its subdirectories.
|
||||||
fixture_builder_file = "#{File.dirname(__FILE__)}/support/fixture_builder.rb"
|
fixture_builder_file = "#{File.dirname(__FILE__)}/support/fixture_builder.rb"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue