Merge pull request #62 from SuperTux88/protocol-next-step

Next step for the new protocol
This commit is contained in:
Benjamin Neff 2017-05-12 03:40:58 +02:00
commit 51f73b86fc
No known key found for this signature in database
GPG key ID: 971464C3F1A90194
65 changed files with 541 additions and 1388 deletions

View file

@ -135,7 +135,7 @@ module DiasporaFederation
private_class_method def self.parse_xrd_document(xrd_doc)
raise ArgumentError unless xrd_doc.instance_of?(String)
doc = Nokogiri::XML::Document.parse(xrd_doc)
doc = Nokogiri::XML(xrd_doc)
raise InvalidDocument, "Not an XRD document" if !doc.root || doc.root.name != "XRD"
doc
end

View file

@ -4,10 +4,6 @@ module DiasporaFederation
#
# @see Validators::CommentValidator
class Comment < Entity
# Old signature order
# @deprecated
LEGACY_SIGNATURE_ORDER = %i(guid parent_guid text author).freeze
# The {Comment} parent is a {Post}
PARENT_TYPE = "Post".freeze
@ -21,6 +17,12 @@ module DiasporaFederation
# Comment entity creation time
# @return [Time] creation time
property :created_at, :timestamp, default: -> { Time.now.utc }
# Remove "created_at" when no order was received.
# @deprecated TODO: Remove this, this will break compatibility with pods older than 0.6.3.0.
def signature_order
@signature_order || super.tap {|order| order.delete(:created_at) }
end
end
end
end

View file

@ -4,19 +4,11 @@ module DiasporaFederation
#
# @see Validators::EventParticipationValidator
class EventParticipation < Entity
# Old signature order
# @deprecated
LEGACY_SIGNATURE_ORDER = %i(author guid parent_guid status).freeze
# The {EventParticipation} parent is an {Event}
PARENT_TYPE = "Event".freeze
include Relayable
# Redefine the author property without +diaspora_handle+ +xml_name+
# @deprecated Can be removed after XMLs are generated with new names
property :author, :string
# @!attribute [r] status
# The participation status of the user
# "accepted", "declined" or "tentative"

View file

@ -4,24 +4,20 @@ module DiasporaFederation
#
# @see Validators::LikeValidator
class Like < Entity
# Old signature order
# @deprecated
LEGACY_SIGNATURE_ORDER = %i(positive guid parent_type parent_guid author).freeze
include Relayable
# @!attribute [r] positive
# If +true+ set a like, if +false+, set a dislike (dislikes are currently not
# implemented in the diaspora* frontend).
# @return [Boolean] is it a like or a dislike
property :positive, :boolean
# @!attribute [r] parent_type
# A string describing the type of the parent
# Can be "Post" or "Comment" (Comments are currently not implemented in the
# diaspora* frontend).
# @return [String] parent type
property :parent_type, :string, xml_name: :target_type
# @!attribute [r] positive
# If +true+ set a like, if +false+, set a dislike (dislikes are currently not
# implemented in the diaspora* frontend).
# @return [Boolean] is it a like or a dislike
property :positive, :boolean
end
end
end

View file

@ -4,11 +4,17 @@ module DiasporaFederation
#
# @see Validators::MessageValidator
class Message < Entity
# Old signature order
# @deprecated
LEGACY_SIGNATURE_ORDER = %i(guid parent_guid text created_at author conversation_guid).freeze
# @!attribute [r] author
# The diaspora* ID of the author
# @see Person#author
# @return [String] diaspora* ID
property :author, :string, xml_name: :diaspora_handle
include Relayable
# @!attribute [r] guid
# A random string of at least 16 chars
# @see Validation::Rule::Guid
# @return [String] guid
property :guid, :string
# @!attribute [r] text
# Text of the message composed by a user
@ -26,55 +32,9 @@ module DiasporaFederation
# @return [String] conversation guid
property :conversation_guid, :string
# It is only valid to receive a {Message} from the author itself,
# or from the author of the parent {Conversation} if the author signature is valid.
# @deprecated remove after {Message} doesn't include {Relayable} anymore
def sender_valid?(sender)
sender == author || (sender == parent_author && verify_author_signature)
end
# @deprecated remove after {Message} doesn't include {Relayable} anymore
def to_h
super.tap {|hash| hash[:created_at] = created_at.utc.iso8601 }
end
# @deprecated remove after {Message} doesn't include {Relayable} anymore
private_class_method def self.xml_parser_class
DiasporaFederation::Parsers::XmlParser
end
# Default implementation, don't verify signatures for a {Message}.
# @see Entity.from_hash
# @deprecated remove after {Message} doesn't include {Relayable} anymore
# @param [Hash] properties hash
# @return [Entity] instance
def self.from_hash(hash)
new({parent_guid: nil, parent: nil}.merge(hash))
end
private
# @deprecated remove after {Message} doesn't include {Relayable} anymore
def verify_author_signature
verify_signature(author, :author_signature)
true
end
# @deprecated remove after {Message} doesn't include {Relayable} anymore
def parent_author
parent = DiasporaFederation.callbacks.trigger(:fetch_related_entity, "Conversation", conversation_guid)
raise Federation::Fetcher::NotFetchable, "parent of #{self} not found" unless parent
parent.author
end
# old timestamp format, because this signature is only used from old pods which also relay with old format
# @deprecated remove after {Message} doesn't include {Relayable} anymore
def normalize_property(name, value)
if name == :created_at
value.to_s
else
super
end
# @return [String] string representation of this object
def to_s
"#{super}:#{conversation_guid}"
end
end
end

View file

@ -4,11 +4,22 @@ module DiasporaFederation
#
# @see Validators::Participation
class Participation < Entity
# Old signature order
# @deprecated
LEGACY_SIGNATURE_ORDER = %i(guid parent_type parent_guid author).freeze
# @!attribute [r] author
# The diaspora* ID of the author
# @see Person#author
# @return [String] diaspora* ID
property :author, :string, xml_name: :diaspora_handle
include Relayable
# @!attribute [r] guid
# A random string of at least 16 chars
# @see Validation::Rule::Guid
# @return [String] guid
property :guid, :string
# @!attribute [r] parent_guid
# @see StatusMessage#guid
# @return [String] parent guid
property :parent_guid, :string
# @!attribute [r] parent_type
# A string describing a type of the target to subscribe on
@ -16,18 +27,9 @@ module DiasporaFederation
# @return [String] parent type
property :parent_type, :string, xml_name: :target_type
# It is only valid to receive a {Participation} from the author themself.
# @deprecated remove after {Participation} doesn't include {Relayable} anymore
def sender_valid?(sender)
sender == author
end
# hackaround hacky from_hash override
# @deprecated remove after {Participation} doesn't include {Relayable} anymore
def enriched_properties
super.tap {|hash|
hash.delete(:parent) if hash[:parent].nil?
}
# @return [String] string representation of this object
def to_s
"#{super}:#{parent_type}:#{parent_guid}"
end
# Validates that the parent exists and the parent author is local
@ -36,22 +38,12 @@ module DiasporaFederation
raise ParentNotLocal, "obj=#{self}" unless parent && parent.local
end
# Don't verify signatures for a {Participation}. Validate that the parent is local.
# Validate that the parent is local.
# @see Entity.from_hash
# @param [Hash] hash entity initialization hash
# @return [Entity] instance
def self.from_hash(hash)
new(hash.merge(parent: nil)).tap(&:validate_parent)
end
# @deprecated remove after {Participation} doesn't include {Relayable} anymore
private_class_method def self.xml_parser_class
DiasporaFederation::Parsers::XmlParser
end
# @deprecated remove after {Participation} doesn't include {Relayable} anymore
private_class_method def self.json_parser_class
DiasporaFederation::Parsers::JsonParser
super.tap(&:validate_parent)
end
# Raised, if the parent is not owned by the receiving pod.

View file

@ -4,10 +4,6 @@ module DiasporaFederation
#
# @see Validators::PollParticipationValidator
class PollParticipation < Entity
# Old signature order
# @deprecated
LEGACY_SIGNATURE_ORDER = %i(guid parent_guid author poll_answer_guid).freeze
# The {PollParticipation} parent is a {Poll}
PARENT_TYPE = "Poll".freeze

View file

@ -7,10 +7,6 @@ module DiasporaFederation
module Relayable
include Signable
# Order from the parsed xml for signature
# @return [Array] order from xml
attr_reader :xml_order
# Additional properties from parsed input object
# @return [Hash] additional elements
attr_reader :additional_data
@ -65,11 +61,11 @@ module DiasporaFederation
# Initializes a new relayable Entity with order and additional xml elements
#
# @param [Hash] data entity data
# @param [Array] xml_order order from xml
# @param [Array] signature_order order for the signature
# @param [Hash] additional_data additional xml elements
# @see DiasporaFederation::Entity#initialize
def initialize(data, xml_order=nil, additional_data={})
@xml_order = xml_order.reject {|name| name =~ /signature/ } if xml_order
def initialize(data, signature_order=nil, additional_data={})
self.signature_order = signature_order if signature_order
@additional_data = additional_data
super(data)
@ -103,6 +99,12 @@ module DiasporaFederation
}
end
# The order for signing
# @return [Array]
def signature_order
@signature_order || self.class.class_props.keys - %i(author_signature parent_author_signature parent)
end
private
# Sign with author key
@ -147,15 +149,10 @@ module DiasporaFederation
order.map {|element| [element, data[element] || ""] }.to_h
end
# The order for signing
# @return [Array]
def signature_order
if xml_order
prop_names = self.class.class_props.keys.map(&:to_s)
xml_order.map {|name| prop_names.include?(name) ? name.to_sym : name }
else
self.class::LEGACY_SIGNATURE_ORDER
end
def signature_order=(order)
prop_names = self.class.class_props.keys.map(&:to_s)
@signature_order = order.reject {|name| name =~ /signature/ }
.map {|name| prop_names.include?(name) ? name.to_sym : name }
end
# @return [String] signature data string

View file

@ -53,50 +53,13 @@ module DiasporaFederation
# @return [String] target author signature
property :target_author_signature, :string, default: nil
# @!attribute [r] target
# Target entity
# @return [RelatedEntity] target entity
entity :target, Entities::RelatedEntity
# Use only {Retraction} for receive
# @return [Retraction] instance as normal retraction
def to_retraction
Retraction.new(author: author, target_guid: target_guid, target_type: target_type, target: target)
end
# @return [String] string representation of this object
def to_s
"RelayableRetraction:#{target_type}:#{target_guid}"
def initialize(*)
raise "Sending RelayableRetraction is not supported anymore! Use Retraction instead!"
end
# @return [Retraction] instance
def self.from_hash(hash)
hash[:target] = Retraction.send(:fetch_target, hash[:target_type], hash[:target_guid])
new(hash).to_retraction
end
private
# It updates also the signatures with the keys of the author and the parent
# if the signatures are not there yet and if the keys are available.
#
# @return [Hash] xml elements with updated signatures
def enriched_properties
privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key, author)
super.tap do |hash|
fill_required_signature(privkey, hash) unless privkey.nil?
end
end
# @param [OpenSSL::PKey::RSA] privkey private key of sender
# @param [Hash] hash hash given for a signing
def fill_required_signature(privkey, hash)
if target.parent.author == author && parent_author_signature.nil?
hash[:parent_author_signature] = SignedRetraction.sign_with_key(privkey, self)
elsif target.author == author && target_author_signature.nil?
hash[:target_author_signature] = SignedRetraction.sign_with_key(privkey, self)
end
Retraction.from_hash(hash)
end
end
end

View file

@ -18,20 +18,13 @@ module DiasporaFederation
# @return [String] recipient ID
property :recipient, :string, xml_name: :recipient_handle
# Use only {Contact} for receive
# @return [Contact] instance as contact
def to_contact
Contact.new(author: author, recipient: recipient)
end
# @return [String] string representation of this object
def to_s
"Request:#{author}:#{recipient}"
def initialize(*)
raise "Sending Request is not supported anymore! Use Contact instead!"
end
# @return [Retraction] instance
def self.from_hash(hash)
super.to_contact
Contact.new(hash)
end
end
end

View file

@ -30,51 +30,13 @@ module DiasporaFederation
# @return [String] author signature
property :target_author_signature, :string, default: nil
# @!attribute [r] target
# Target entity
# @return [RelatedEntity] target entity
entity :target, Entities::RelatedEntity
# Use only {Retraction} for receive
# @return [Retraction] instance as normal retraction
def to_retraction
Retraction.new(author: author, target_guid: target_guid, target_type: target_type, target: target)
end
# Create signature for a retraction
# @param [OpenSSL::PKey::RSA] privkey private key of sender
# @param [SignedRetraction, RelayableRetraction] ret the retraction to sign
# @return [String] a Base64 encoded signature of the retraction with the key
def self.sign_with_key(privkey, ret)
Base64.strict_encode64(privkey.sign(Relayable::DIGEST, [ret.target_guid, ret.target_type].join(";")))
end
# @return [String] string representation of this object
def to_s
"SignedRetraction:#{target_type}:#{target_guid}"
def initialize(*)
raise "Sending SignedRetraction is not supported anymore! Use Retraction instead!"
end
# @return [Retraction] instance
def self.from_hash(hash)
hash[:target] = Retraction.send(:fetch_target, hash[:target_type], hash[:target_guid])
new(hash).to_retraction
end
private
# It updates also the signatures with the keys of the author and the parent
# if the signatures are not there yet and if the keys are available.
#
# @return [Hash] xml elements with updated signatures
def enriched_properties
super.tap do |hash|
hash[:target_author_signature] = target_author_signature || sign_with_author.to_s
end
end
def sign_with_author
privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key, author)
SignedRetraction.sign_with_key(privkey, self) unless privkey.nil?
Retraction.from_hash(hash)
end
end
end

View file

@ -90,7 +90,6 @@ module DiasporaFederation
# {http://www.rubydoc.info/gems/nokogiri/Nokogiri/XML/Element Nokogiri::XML::Element}s
#
# @see Nokogiri::XML::Node.to_xml
# @see XmlPayload#pack
#
# @return [Nokogiri::XML::Element] root element containing properties as child elements
def to_xml
@ -296,8 +295,7 @@ module DiasporaFederation
# Create simple node, fill it with text and append to root
def simple_node(doc, name, value)
xml_name = self.class.xml_names[name]
Nokogiri::XML::Element.new(xml_name ? xml_name.to_s : name, doc).tap do |node|
Nokogiri::XML::Element.new(name.to_s, doc).tap do |node|
node.content = value.gsub(INVALID_XML_REGEX, "\uFFFD") unless value.empty?
end
end

View file

@ -13,7 +13,7 @@ module DiasporaFederation
response = HttpClient.get(url)
raise "Failed to fetch #{url}: #{response.status}" unless response.success?
magic_env_xml = Nokogiri::XML::Document.parse(response.body).root
magic_env_xml = Nokogiri::XML(response.body).root
magic_env = Salmon::MagicEnvelope.unenvelop(magic_env_xml)
Receiver::Public.new(magic_env).receive
rescue => e

View file

@ -11,7 +11,7 @@ module DiasporaFederation
magic_env = if legacy
Salmon::Slap.from_xml(data)
else
magic_env_xml = Nokogiri::XML::Document.parse(data).root
magic_env_xml = Nokogiri::XML(data).root
Salmon::MagicEnvelope.unenvelop(magic_env_xml)
end
Public.new(magic_env).receive

View file

@ -11,7 +11,7 @@ module DiasporaFederation
# @return [Array<String>] url to retry
def self.public(sender_id, obj_str, urls, xml)
hydra = HydraWrapper.new(sender_id, obj_str)
urls.each {|url| hydra.insert_job(url, xml) }
urls.each {|url| hydra.insert_magic_env_request(url, xml) }
hydra.send
end
@ -23,7 +23,7 @@ module DiasporaFederation
# @return [Hash] targets to retry
def self.private(sender_id, obj_str, targets)
hydra = HydraWrapper.new(sender_id, obj_str)
targets.each {|url, xml| hydra.insert_job(url, xml) }
targets.each {|url, json| hydra.insert_enc_magic_env_request(url, json) }
hydra.send.map {|url| [url, targets[url]] }.to_h
end
end

View file

@ -17,12 +17,21 @@ module DiasporaFederation
method: :post,
verbose: DiasporaFederation.http_verbose,
cainfo: DiasporaFederation.certificate_authorities,
forbid_reuse: true,
headers: {
"Expect" => "",
"Transfer-Encoding" => "",
"User-Agent" => DiasporaFederation.http_user_agent
}
forbid_reuse: true
}
end
def self.xml_headers
@xml_headers ||= {
"Content-Type" => "application/magic-envelope+xml",
"User-Agent" => DiasporaFederation.http_user_agent
}
end
def self.json_headers
@json_headers ||= {
"Content-Type" => "application/json",
"User-Agent" => DiasporaFederation.http_user_agent
}
end
@ -36,13 +45,18 @@ module DiasporaFederation
@urls_to_retry = []
end
# Prepares and inserts job into the hydra queue
# Prepares and inserts a public MagicEnvelope job into the hydra queue
# @param [String] url the receive-url for the xml
# @param [String] xml xml salmon message
def insert_job(url, xml)
request = Typhoeus::Request.new(url, HydraWrapper.hydra_opts.merge(body: {xml: xml}))
prepare_request(request)
hydra.queue(request)
# @param [String] xml MagicEnvelope xml
def insert_magic_env_request(url, xml)
insert_job(url, HydraWrapper.hydra_opts.merge(body: xml, headers: HydraWrapper.xml_headers))
end
# Prepares and inserts a private encrypted MagicEnvelope job into the hydra queue
# @param [String] url the receive-url for the message
# @param [String] json encrypted MagicEnvelope json
def insert_enc_magic_env_request(url, json)
insert_job(url, HydraWrapper.hydra_opts.merge(body: json, headers: HydraWrapper.json_headers))
end
# Sends all queued messages
@ -54,6 +68,15 @@ module DiasporaFederation
private
# Prepares and inserts job into the hydra queue
# @param [String] url the receive-url for the message
# @param [Hash] options request options
def insert_job(url, options)
request = Typhoeus::Request.new(url, options)
prepare_request(request)
hydra.queue(request)
end
# @return [Typhoeus::Hydra] hydra
def hydra
@hydra ||= Typhoeus::Hydra.new(max_concurrency: DiasporaFederation.http_concurrency)

View file

@ -54,7 +54,7 @@ module DiasporaFederation
key = encoded_key.map {|k, v| [k, Base64.decode64(v)] }.to_h
xml = AES.decrypt(encrypted_json["encrypted_magic_envelope"], key["key"], key["iv"])
Nokogiri::XML::Document.parse(xml).root
Nokogiri::XML(xml).root
end
end
end

View file

@ -48,32 +48,12 @@ module DiasporaFederation
# Finally, before decrypting the magic envelope payload, the signature should
# first be verified.
#
# @example Generating an encrypted Salmon Slap
# author_id = "author@pod.example.tld"
# author_privkey = however_you_retrieve_the_authors_private_key(author_id)
# recipient_pubkey = however_you_retrieve_the_recipients_public_key()
# entity = YourEntity.new(attr: "val")
#
# slap_xml = EncryptedSlap.prepare(author_id, author_privkey, entity).generate_xml(recipient_pubkey)
#
# @example Parsing a Salmon Slap
# recipient_privkey = however_you_retrieve_the_recipients_private_key()
# entity = EncryptedSlap.from_xml(slap_xml, recipient_privkey).payload
#
# @deprecated
class EncryptedSlap < Slap
# the author of the slap
# @param [String] value the author diaspora* ID
attr_writer :author_id
# the key and iv if it is an encrypted slap
# @param [Hash] value hash containing the key and iv
attr_writer :cipher_params
# the prepared encrypted magic envelope xml
# @param [Nokogiri::XML::Element] value magic envelope xml
attr_writer :magic_envelope_xml
# Creates a {MagicEnvelope} instance from the data within the given XML string
# containing an encrypted payload.
#
@ -87,7 +67,7 @@ module DiasporaFederation
# @raise [MissingMagicEnvelope] if the +me:env+ element is missing in the XML
def self.from_xml(slap_xml, privkey)
raise ArgumentError unless slap_xml.instance_of?(String) && privkey.instance_of?(OpenSSL::PKey::RSA)
doc = Nokogiri::XML::Document.parse(slap_xml)
doc = Nokogiri::XML(slap_xml)
header_elem = doc.at_xpath("d:diaspora/d:encrypted_header", Slap::NS)
raise MissingHeader if header_elem.nil?
@ -98,44 +78,6 @@ module DiasporaFederation
MagicEnvelope.unenvelop(magic_env_from_doc(doc), sender, cipher_params)
end
# Creates an encrypted Salmon Slap.
#
# @param [String] author_id diaspora* ID of the author
# @param [OpenSSL::PKey::RSA] privkey sender private key for signing the magic envelope
# @param [Entity] entity payload
# @return [EncryptedSlap] encrypted Slap instance
# @raise [ArgumentError] if any of the arguments is of the wrong type
def self.prepare(author_id, privkey, entity)
raise ArgumentError unless author_id.instance_of?(String) &&
privkey.instance_of?(OpenSSL::PKey::RSA) &&
entity.is_a?(Entity)
EncryptedSlap.new.tap do |slap|
slap.author_id = author_id
magic_envelope = MagicEnvelope.new(entity)
slap.cipher_params = magic_envelope.encrypt!
slap.magic_envelope_xml = magic_envelope.envelop(privkey)
end
end
# Creates an encrypted Salmon Slap XML string.
#
# @param [OpenSSL::PKey::RSA] pubkey recipient public key for encrypting the AES key
# @return [String] Salmon XML string
# @raise [ArgumentError] if any of the arguments is of the wrong type
def generate_xml(pubkey)
raise ArgumentError unless pubkey.instance_of?(OpenSSL::PKey::RSA)
Slap.build_xml do |xml|
xml.encrypted_header(encrypted_header(@author_id, @cipher_params, pubkey))
xml.parent << @magic_envelope_xml
end
end
private
# Decrypts and reads the data from the encrypted XML header
# @param [String] data base64 encoded, encrypted header data
# @param [OpenSSL::PKey::RSA] privkey private key for decryption
@ -160,46 +102,7 @@ module DiasporaFederation
key = JSON.parse(privkey.private_decrypt(Base64.decode64(cipher_header["aes_key"])))
xml = AES.decrypt(cipher_header["ciphertext"], Base64.decode64(key["key"]), Base64.decode64(key["iv"]))
Nokogiri::XML::Document.parse(xml).root
end
# Encrypt the header xml with an AES cipher and encrypt the cipher params
# with the recipients public_key.
# @param [String] author_id diaspora_handle
# @param [Hash] envelope_key envelope cipher params
# @param [OpenSSL::PKey::RSA] pubkey recipient public_key
# @return [String] encrypted base64 encoded header
def encrypted_header(author_id, envelope_key, pubkey)
data = header_xml(author_id, strict_base64_encode(envelope_key))
header_key = AES.generate_key_and_iv
ciphertext = AES.encrypt(data, header_key[:key], header_key[:iv])
json_key = JSON.generate(strict_base64_encode(header_key))
encrypted_key = Base64.strict_encode64(pubkey.public_encrypt(json_key))
json_header = JSON.generate(aes_key: encrypted_key, ciphertext: ciphertext)
Base64.strict_encode64(json_header)
end
# Generate the header xml string, including the author, aes_key and iv
# @param [String] author_id diaspora_handle of the author
# @param [Hash] envelope_key { key: "...", iv: "..." } (values in base64)
# @return [String] header XML string
def header_xml(author_id, envelope_key)
@header_xml ||= Nokogiri::XML::Builder.new(encoding: "UTF-8") {|xml|
xml.decrypted_header {
xml.iv(envelope_key[:iv])
xml.aes_key(envelope_key[:key])
xml.author_id(author_id)
}
}.to_xml.strip
end
# @param [Hash] hash { key: "...", iv: "..." }
# @return [Hash] encoded hash: { key: "...", iv: "..." }
def strict_base64_encode(hash)
hash.map {|k, v| [k, Base64.strict_encode64(v)] }.to_h
Nokogiri::XML(xml).root
end
end
end

View file

@ -76,22 +76,6 @@ module DiasporaFederation
}
end
# Encrypts the payload with a new, random AES cipher and returns the cipher
# params that were used.
#
# This must happen after the MagicEnvelope instance was created and before
# {MagicEnvelope#envelop} is called.
#
# @see AES#generate_key_and_iv
# @see AES#encrypt
#
# @return [Hash] AES key and iv. E.g.: { key: "...", iv: "..." }
def encrypt!
AES.generate_key_and_iv.tap do |key|
@payload_data = AES.encrypt(payload_data, key[:key], key[:iv])
end
end
# Extracts the entity encoded in the magic envelope data, if the signature
# is valid. If +cipher_params+ is given, also attempts to decrypt the payload first.
#
@ -128,7 +112,7 @@ module DiasporaFederation
logger.debug "unenvelop message from #{sender}:\n#{data}"
new(XmlPayload.unpack(Nokogiri::XML::Document.parse(data).root), sender)
new(XmlPayload.unpack(Nokogiri::XML(data).root), sender)
end
private
@ -136,7 +120,7 @@ module DiasporaFederation
# The payload data as string
# @return [String] payload data
def payload_data
@payload_data ||= XmlPayload.pack(@payload).to_xml.strip.tap do |data|
@payload_data ||= payload.to_xml.to_xml.strip.tap do |data|
logger.debug "send payload:\n#{data}"
end
end
@ -153,7 +137,7 @@ module DiasporaFederation
def build_xml
Nokogiri::XML::Builder.new(encoding: "UTF-8") {|xml|
yield xml
}.doc.root
}.doc
end
# Creates the signature for all fields according to specification

View file

@ -13,13 +13,6 @@ module DiasporaFederation
# {magic_envelope}
# </diaspora>
#
# @example Generating a Salmon Slap
# author_id = "author@pod.example.tld"
# author_privkey = however_you_retrieve_the_authors_private_key(author_id)
# entity = YourEntity.new(attr: "val")
#
# slap_xml = Slap.generate_xml(author_id, author_privkey, entity)
#
# @example Parsing a Salmon Slap
# entity = Slap.from_xml(slap_xml).payload
#
@ -40,7 +33,7 @@ module DiasporaFederation
# @raise [MissingMagicEnvelope] if the +me:env+ element is missing from the XML
def self.from_xml(slap_xml)
raise ArgumentError unless slap_xml.instance_of?(String)
doc = Nokogiri::XML::Document.parse(slap_xml)
doc = Nokogiri::XML(slap_xml)
author_elem = doc.at_xpath("d:diaspora/d:header/d:author_id", Slap::NS)
raise MissingAuthor if author_elem.nil? || author_elem.content.empty?
@ -49,40 +42,6 @@ module DiasporaFederation
MagicEnvelope.unenvelop(magic_env_from_doc(doc), sender)
end
# Creates an unencrypted Salmon Slap and returns the XML string.
#
# @param [String] author_id diaspora* ID of the author
# @param [OpenSSL::PKey::RSA] privkey sender private_key for signing the magic envelope
# @param [Entity] entity payload
# @return [String] Salmon XML string
# @raise [ArgumentError] if any of the arguments is not the correct type
def self.generate_xml(author_id, privkey, entity)
raise ArgumentError unless author_id.instance_of?(String) &&
privkey.instance_of?(OpenSSL::PKey::RSA) &&
entity.is_a?(Entity)
build_xml do |xml|
xml.header {
xml.author_id(author_id)
}
xml.parent << MagicEnvelope.new(entity, author_id).envelop(privkey)
end
end
# Builds the xml for the Salmon Slap.
#
# @yield [xml] Invokes the block with the
# {http://www.rubydoc.info/gems/nokogiri/Nokogiri/XML/Builder Nokogiri::XML::Builder}
# @return [String] Slap XML
def self.build_xml
Nokogiri::XML::Builder.new(encoding: "UTF-8") {|xml|
xml.diaspora("xmlns" => Salmon::XMLNS, "xmlns:me" => MagicEnvelope::XMLNS) {
yield xml
}
}.to_xml
end
# Parses the magic envelop from the document.
#
# @param [Nokogiri::XML::Document] doc Salmon XML Document

View file

@ -13,25 +13,6 @@ module DiasporaFederation
# (The +post+ element is there for historic reasons...)
# @deprecated
module XmlPayload
# Encapsulates an Entity inside the wrapping xml structure
# and returns the XML Object.
#
# @param [Entity] entity subject
# @return [Nokogiri::XML::Element] XML root node
# @raise [ArgumentError] if the argument is not an Entity subclass
def self.pack(entity)
raise ArgumentError, "only instances of DiasporaFederation::Entity allowed" unless entity.is_a?(Entity)
entity_xml = entity.to_xml
doc = entity_xml.document
wrap = Nokogiri::XML::Element.new("XML", doc)
wrap_post = Nokogiri::XML::Element.new("post", doc)
entity_xml.parent = wrap_post
wrap << wrap_post
wrap
end
# Extracts the Entity XML from the wrapping XML structure, parses the entity
# XML and returns a new instance of the Entity that was packed inside the
# given payload.

View file

@ -110,24 +110,22 @@
},
"participation": {
"allOf": [
{"$ref": "#/definitions/relayable"},
{
"type": "object",
"properties": {
"entity_type": {
"type": "string",
"pattern": "^participation$"
},
"entity_data": {
"type": "object",
"properties": {
"entity_type": {
"type": "string",
"pattern": "^participation$"
},
"entity_data": {
"type": "object",
"properties": {
"parent_type": {"enum": ["Post"]}
}
}
"author": { "type": "string" },
"guid": { "$ref": "#/definitions/guid" },
"parent_guid": { "$ref": "#/definitions/guid" },
"parent_type": {"enum": ["Post"]}
}
}
]
}
},
"poll_participation": {

View file

@ -93,10 +93,10 @@ module DiasporaFederation
parent { Fabricate(:related_entity) }
end
Fabricator(:participation_entity,
class_name: DiasporaFederation::Entities::Participation, from: :relayable_entity) do
Fabricator(:participation_entity, class_name: DiasporaFederation::Entities::Participation) do
author { Fabricate.sequence(:diaspora_id) }
guid { Fabricate.sequence(:guid) }
parent_guid { Fabricate.sequence(:guid) }
parent_type "Post"
end
@ -108,11 +108,6 @@ module DiasporaFederation
created_at { Time.now.utc }
end
Fabricator(:request_entity, class_name: DiasporaFederation::Entities::Request) do
author { Fabricate.sequence(:diaspora_id) }
recipient { Fabricate.sequence(:diaspora_id) }
end
Fabricator(:contact_entity, class_name: DiasporaFederation::Entities::Contact) do
author { Fabricate.sequence(:diaspora_id) }
recipient { Fabricate.sequence(:diaspora_id) }
@ -142,7 +137,7 @@ module DiasporaFederation
participants { Array.new(3) { Fabricate.sequence(:diaspora_id) }.join(";") }
end
Fabricator(:message_entity, class_name: DiasporaFederation::Entities::Message, from: :relayable_entity) do
Fabricator(:message_entity, class_name: DiasporaFederation::Entities::Message) do
guid { Fabricate.sequence(:guid) }
author { Fabricate.sequence(:diaspora_id) }
text "this is a very informative text"
@ -150,13 +145,6 @@ module DiasporaFederation
conversation_guid { Fabricate.sequence(:guid) }
end
Fabricator(:relayable_retraction_entity, class_name: DiasporaFederation::Entities::RelayableRetraction) do
author { Fabricate.sequence(:diaspora_id) }
target_guid { Fabricate.sequence(:guid) }
target_type "Comment"
target {|attrs| Fabricate(:related_entity, author: attrs[:author]) }
end
Fabricator(:reshare_entity, class_name: DiasporaFederation::Entities::Reshare) do
root_author { Fabricate.sequence(:diaspora_id) }
root_guid { Fabricate.sequence(:guid) }
@ -174,13 +162,6 @@ module DiasporaFederation
target {|attrs| Fabricate(:related_entity, author: attrs[:author]) }
end
Fabricator(:signed_retraction_entity, class_name: DiasporaFederation::Entities::SignedRetraction) do
author { Fabricate.sequence(:diaspora_id) }
target_guid { Fabricate.sequence(:guid) }
target_type "Post"
target {|attrs| Fabricate(:related_entity, author: attrs[:author]) }
end
Fabricator(:poll_answer_entity, class_name: DiasporaFederation::Entities::PollAnswer) do
guid { Fabricate.sequence(:guid) }
answer { "Obama is a bicycle" }

View file

@ -62,8 +62,3 @@ require "diaspora_federation/validators/reshare_validator"
require "diaspora_federation/validators/retraction_validator"
require "diaspora_federation/validators/status_message_validator"
require "diaspora_federation/validators/web_finger_validator"
# deprecated
require "diaspora_federation/validators/relayable_retraction_validator"
require "diaspora_federation/validators/request_validator"
require "diaspora_federation/validators/signed_retraction_validator"

View file

@ -1,15 +0,0 @@
module DiasporaFederation
module Validators
# This validates a {Entities::RelayableRetraction}.
# @deprecated The {Entities::RelayableRetraction} will be replaced with {Entities::Retraction}.
class RelayableRetractionValidator < Validation::Validator
include Validation
rule :target_guid, :guid
rule :target_type, :not_empty
rule :target, :not_nil
rule :author, %i(not_empty diaspora_id)
end
end
end

View file

@ -1,12 +0,0 @@
module DiasporaFederation
module Validators
# This validates a {Entities::Request}.
# @deprecated The {Entities::Request} will be replaced with {Entities::Contact}.
class RequestValidator < Validation::Validator
include Validation
rule :author, %i(not_empty diaspora_id)
rule :recipient, %i(not_empty diaspora_id)
end
end
end

View file

@ -1,15 +0,0 @@
module DiasporaFederation
module Validators
# This validates a {Entities::SignedRetraction}.
# @deprecated The {Entities::RelayableRetraction} will be replaced with {Entities::Retraction}.
class SignedRetractionValidator < Validation::Validator
include Validation
rule :target_guid, :guid
rule :target_type, :not_empty
rule :target, :not_nil
rule :author, %i(not_empty diaspora_id)
end
end
end

View file

@ -14,7 +14,7 @@ module DiasporaFederation
expect_callback(:fetch_public_key, alice.diaspora_id).and_return(alice.public_key)
magic_env_xml = Nokogiri::XML::Document.parse(response.body).root
magic_env_xml = Nokogiri::XML(response.body).root
magic_env = Salmon::MagicEnvelope.unenvelop(magic_env_xml)
entity = magic_env.payload
@ -33,7 +33,7 @@ module DiasporaFederation
expect_callback(:fetch_public_key, alice.diaspora_id).and_return(alice.public_key)
magic_env_xml = Nokogiri::XML::Document.parse(response.body).root
magic_env_xml = Nokogiri::XML(response.body).root
magic_env = Salmon::MagicEnvelope.unenvelop(magic_env_xml)
entity = magic_env.payload

View file

@ -58,17 +58,11 @@ module DiasporaFederation
end
class SomeRelayable < DiasporaFederation::Entity
LEGACY_SIGNATURE_ORDER = %i(guid author property parent_guid).freeze
PARENT_TYPE = "Parent".freeze
include Entities::Relayable
property :property, :string
def parent_type
PARENT_TYPE
end
end
end

View file

@ -63,7 +63,7 @@ KEY
)
}
let(:legacy_comment_xml_alice) { <<-XML }
let(:legacy_format_comment_xml_alice) { <<-XML }
<XML>
<post>
<comment>
@ -77,7 +77,7 @@ KEY
</post>
</XML>
XML
let(:new_signature_comment_xml_alice) { <<-XML }
let(:new_format_comment_xml_alice) { <<-XML }
<comment>
<author>alice@pod-a.org</author>
<guid>e21589b0b41101333b870f77ba60fa73</guid>
@ -99,7 +99,7 @@ XML
</comment>
XML
let(:legacy_comment_xml_bob) { <<-XML }
let(:legacy_format_comment_xml_bob) { <<-XML }
<XML>
<post>
<comment>
@ -113,7 +113,17 @@ XML
</post>
</XML>
XML
let(:legacy_new_signature_comment_xml_bob) { <<-XML }
let(:legacy_order_new_format_comment_xml_bob) { <<-XML }
<comment>
<guid>e21589b0b41101333b870f77ba60fa73</guid>
<parent_guid>9e269ae0b41201333b8c0f77ba60fa73</parent_guid>
<text>this is a very informative comment</text>
<author>alice@pod-a.org</author>
<author_signature>XU5X1uqTh8SY6JMG9uhEVR5Rg7FURV6lpRwl/HYOu6DJ3Hd9tpA2aSFFibUxxsMgJXKNrrc5SykrrEdTiQoEei+j0QqZf3B5R7r84qgK7M46KazwIpqRPwVl2MdA/0DdQyYJLA/oavNj1nwll9vtR87M7e/C94qG6P+iQTMBQzo=</author_signature>
<parent_author_signature>QqWSdwpb+/dcJUxuKKVe7aiz1NivXzlIdWZ71xyrxnhFxFYd+7EIittyTcp1cVehjg96pwDbn++P/rWyCffqenWu025DHvUfSmQkC93Z0dX6r3OIUlZqwEggtOdbunybiE++F3BVsGt5wC4YbAESB5ZFuhFVhBXh1X+EaZ/qoKo=</parent_author_signature>
</comment>
XML
let(:new_order_legacy_format_comment_xml_bob) { <<-XML }
<XML>
<post>
<comment>
@ -127,7 +137,7 @@ XML
</post>
</XML>
XML
let(:new_signature_comment_xml_bob) { <<-XML }
let(:new_format_comment_xml_bob) { <<-XML }
<comment>
<author>alice@pod-a.org</author>
<guid>e21589b0b41101333b870f77ba60fa73</guid>
@ -137,7 +147,7 @@ XML
<parent_author_signature>hWsagsczmZD6d36d6MFdTt3hKAdnRtupSIU6464G2kkMJ+WlExxMgbF6kWR+jVCBTeKipWCYK3Arnj0YkuIZM9d14bJGVMTsW/ZzNfJ69bXZhsyawI8dPnZnLVydo+hU/XmGJBEuh2TOj9Emq6/HCYiWzPTF5qhYAtyJ1oxJ4Yk=</parent_author_signature>
</comment>
XML
let(:legacy_new_data_comment_xml_bob) { <<-XML }
let(:legacy_format_new_data_comment_xml_bob) { <<-XML }
<XML>
<post>
<comment>
@ -178,7 +188,7 @@ XML
expect_callback(:fetch_private_key, parent.author).and_return(parent_key)
expect_callback(:fetch_related_entity, "Post", parent_guid).and_return(parent)
xml = Nokogiri::XML::Document.parse(new_data_comment_xml_alice).root
xml = Nokogiri::XML(new_data_comment_xml_alice).root
Salmon::XmlPayload.unpack(xml).to_xml
end
end
@ -190,22 +200,22 @@ XML
expect_callback(:fetch_related_entity, "Post", parent_guid).and_return(parent)
end
it "relays legacy signatures and xml" do
xml = Nokogiri::XML::Document.parse(legacy_comment_xml_alice).root
it "relays legacy order" do
xml = Nokogiri::XML(legacy_format_comment_xml_alice).root
entity = Salmon::XmlPayload.unpack(xml)
expect(Salmon::XmlPayload.pack(entity).to_xml).to eq(legacy_comment_xml_bob.strip)
expect(entity.to_xml.to_xml).to eq(legacy_order_new_format_comment_xml_bob.strip)
end
it "relays new signatures and xml" do
xml = Nokogiri::XML::Document.parse(new_signature_comment_xml_alice).root
it "relays new order" do
xml = Nokogiri::XML(new_format_comment_xml_alice).root
entity = Salmon::XmlPayload.unpack(xml)
expect(Salmon::XmlPayload.pack(entity).to_xml).to eq(legacy_new_signature_comment_xml_bob.strip)
expect(entity.to_xml.to_xml).to eq(new_format_comment_xml_bob.strip)
end
it "relays new signatures with new data" do
xml = Nokogiri::XML::Document.parse(new_data_comment_xml_alice).root
it "relays new data" do
xml = Nokogiri::XML(new_data_comment_xml_alice).root
entity = Salmon::XmlPayload.unpack(xml)
expect(Salmon::XmlPayload.pack(entity).to_xml).to eq(legacy_new_data_comment_xml_bob.strip)
expect(entity.to_xml.to_xml).to eq(new_data_comment_xml_bob.strip)
end
end
@ -218,32 +228,40 @@ XML
expect_callback(:fetch_related_entity, "Post", parent_guid).and_return(parent)
end
it "parses legacy signatures and xml" do
xml = Nokogiri::XML::Document.parse(legacy_comment_xml_bob).root
it "parses legacy format" do
xml = Nokogiri::XML(legacy_format_comment_xml_bob).root
entity = Salmon::XmlPayload.unpack(xml)
expect(entity.author).to eq(author)
expect(entity.text).to eq(text)
end
it "parses new signatures with legacy xml" do
xml = Nokogiri::XML::Document.parse(legacy_new_signature_comment_xml_bob).root
it "parses legacy order with new xml format" do
xml = Nokogiri::XML(legacy_order_new_format_comment_xml_bob).root
entity = Salmon::XmlPayload.unpack(xml)
expect(entity.author).to eq(author)
expect(entity.text).to eq(text)
end
it "parses new signatures and xml" do
xml = Nokogiri::XML::Document.parse(new_signature_comment_xml_bob).root
it "parses new order with legacy xml format" do
xml = Nokogiri::XML(new_order_legacy_format_comment_xml_bob).root
entity = Salmon::XmlPayload.unpack(xml)
expect(entity.author).to eq(author)
expect(entity.text).to eq(text)
end
it "parses new data with legacy xml" do
xml = Nokogiri::XML::Document.parse(legacy_new_data_comment_xml_bob).root
it "parses new xml format" do
xml = Nokogiri::XML(new_format_comment_xml_bob).root
entity = Salmon::XmlPayload.unpack(xml)
expect(entity.author).to eq(author)
expect(entity.text).to eq(text)
end
it "parses new data with legacy xml format" do
xml = Nokogiri::XML(legacy_format_new_data_comment_xml_bob).root
entity = Salmon::XmlPayload.unpack(xml)
expect(entity.author).to eq(author)
@ -251,8 +269,8 @@ XML
expect(entity.additional_data["new_data"]).to eq(new_data)
end
it "parses new xml with additional data" do
xml = Nokogiri::XML::Document.parse(new_data_comment_xml_bob).root
it "parses new data with new xml format" do
xml = Nokogiri::XML(new_data_comment_xml_bob).root
entity = Salmon::XmlPayload.unpack(xml)
expect(entity.author).to eq(author)

View file

@ -4,7 +4,7 @@ module DiasporaFederation
let(:xml) { <<-XML }
<account_deletion>
<diaspora_handle>#{data[:author]}</diaspora_handle>
<author>#{data[:author]}</author>
</account_deletion>
XML

View file

@ -19,7 +19,7 @@ module DiasporaFederation
<account_migration>
<author>#{data[:author]}</author>
<profile>
<diaspora_handle>#{data[:profile].author}</diaspora_handle>
<author>#{data[:profile].author}</author>
<first_name>#{data[:profile].first_name}</first_name>
<last_name/>
<image_url>#{data[:profile].image_url}</image_url>

View file

@ -15,10 +15,10 @@ module DiasporaFederation
let(:xml) { <<-XML }
<comment>
<author>#{data[:author]}</author>
<guid>#{data[:guid]}</guid>
<parent_guid>#{parent.guid}</parent_guid>
<text>#{data[:text]}</text>
<diaspora_handle>#{data[:author]}</diaspora_handle>
<author_signature>#{data[:author_signature]}</author_signature>
<parent_author_signature>#{data[:parent_author_signature]}</parent_author_signature>
</comment>
@ -37,10 +37,10 @@ XML
"created_at": "#{data[:created_at].iso8601}"
},
"property_order": [
"author",
"guid",
"parent_guid",
"text",
"author"
"text"
]
}
JSON
@ -59,7 +59,7 @@ JSON
describe "#created_at" do
it "has a created_at after parse" do
entity = described_class.from_xml(Nokogiri::XML::Document.parse(xml).root)
entity = described_class.from_xml(Nokogiri::XML(xml).root)
expect(entity.created_at).to be_within(1).of(Time.now.utc)
end

View file

@ -29,11 +29,11 @@ module DiasporaFederation
let(:xml) { <<-XML }
<conversation>
<diaspora_handle>#{data[:author]}</diaspora_handle>
<author>#{data[:author]}</author>
<guid>#{parent.guid}</guid>
<subject>#{data[:subject]}</subject>
<created_at>#{data[:created_at].utc.iso8601}</created_at>
<participant_handles>#{data[:participants]}</participant_handles>
<participants>#{data[:participants]}</participants>
#{data[:messages].map {|a| indent(a.to_xml.to_s, 2) }.join("\n")}
</conversation>
XML
@ -56,7 +56,7 @@ XML
</conversation>
XML
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(minimal_xml).root)
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML(minimal_xml).root)
expect(parsed_instance.messages).to eq([])
end
end

View file

@ -40,7 +40,7 @@ XML
</event>
XML
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(minimal_xml).root)
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML(minimal_xml).root)
expect(parsed_instance.end).to be_nil
expect(parsed_instance.all_day).to be_falsey
expect(parsed_instance.timezone).to be_nil

View file

@ -14,11 +14,11 @@ module DiasporaFederation
let(:xml) { <<-XML }
<like>
<positive>#{data[:positive]}</positive>
<author>#{data[:author]}</author>
<guid>#{data[:guid]}</guid>
<target_type>#{parent.entity_type}</target_type>
<parent_guid>#{parent.guid}</parent_guid>
<diaspora_handle>#{data[:author]}</diaspora_handle>
<parent_type>#{parent.entity_type}</parent_type>
<positive>#{data[:positive]}</positive>
<author_signature>#{data[:author_signature]}</author_signature>
<parent_author_signature>#{data[:parent_author_signature]}</parent_author_signature>
</like>
@ -33,15 +33,15 @@ XML
"parent_guid": "#{parent.guid}",
"author_signature": "#{data[:author_signature]}",
"parent_author_signature": "#{data[:parent_author_signature]}",
"positive": #{data[:positive]},
"parent_type": "#{parent.entity_type}"
"parent_type": "#{parent.entity_type}",
"positive": #{data[:positive]}
},
"property_order": [
"positive",
"author",
"guid",
"parent_type",
"parent_guid",
"author"
"parent_type",
"positive"
]
}
JSON
@ -69,7 +69,7 @@ JSON
XML
expect {
DiasporaFederation::Entities::Like.from_xml(Nokogiri::XML::Document.parse(broken_xml).root)
DiasporaFederation::Entities::Like.from_xml(Nokogiri::XML(broken_xml).root)
}.to raise_error Entity::ValidationError, "invalid DiasporaFederation::Entities::Like! missing 'parent_type'."
end
@ -83,7 +83,7 @@ XML
XML
expect {
DiasporaFederation::Entities::Like.from_xml(Nokogiri::XML::Document.parse(broken_xml).root)
DiasporaFederation::Entities::Like.from_xml(Nokogiri::XML(broken_xml).root)
}.to raise_error Entity::ValidationError, "invalid DiasporaFederation::Entities::Like! missing 'parent_guid'."
end
end

View file

@ -1,108 +1,21 @@
module DiasporaFederation
describe Entities::Message do
let(:parent) { Fabricate(:conversation, author: bob) }
let(:parent_entity) { Fabricate(:related_entity, author: bob.diaspora_id) }
let(:data) {
Fabricate
.attributes_for(:message_entity, author: alice.diaspora_id, parent_guid: parent.guid, parent: parent_entity)
.tap {|hash| add_signatures(hash) }
}
let(:data) { Fabricate.attributes_for(:message_entity, author: alice.diaspora_id) }
let(:xml) { <<-XML }
<message>
<guid>#{data[:guid]}</guid>
<parent_guid>#{parent.guid}</parent_guid>
<text>#{data[:text]}</text>
<created_at>#{data[:created_at]}</created_at>
<diaspora_handle>#{data[:author]}</diaspora_handle>
<conversation_guid>#{data[:conversation_guid]}</conversation_guid>
<author_signature>#{data[:author_signature]}</author_signature>
<parent_author_signature>#{data[:parent_author_signature]}</parent_author_signature>
</message>
XML
let(:string) { "Message:#{data[:guid]}:#{parent.guid}" }
it_behaves_like "an Entity subclass"
it_behaves_like "an XML Entity", %i(parent parent_guid)
it_behaves_like "a relayable Entity"
describe "#sender_valid?" do
let(:entity) { Entities::Message.new(data) }
it "allows the author" do
expect(entity.sender_valid?(alice.diaspora_id)).to be_truthy
end
it "allows parent author if the signature is valid" do
expect_callback(:fetch_related_entity, "Conversation", entity.conversation_guid).and_return(parent_entity)
expect_callback(:fetch_public_key, alice.diaspora_id).and_return(alice.private_key)
expect(entity.sender_valid?(bob.diaspora_id)).to be_truthy
end
it "does not allow any other person" do
expect_callback(:fetch_related_entity, "Conversation", entity.conversation_guid).and_return(parent_entity)
invalid_sender = Fabricate.sequence(:diaspora_id)
expect(entity.sender_valid?(invalid_sender)).to be_falsey
end
it "does not allow the parent author if the signature is invalid" do
expect_callback(:fetch_related_entity, "Conversation", entity.conversation_guid).and_return(parent_entity)
expect_callback(:fetch_public_key, alice.diaspora_id).and_return(alice.private_key)
invalid_msg = Entities::Message.new(data.merge(author_signature: "aa"))
expect {
invalid_msg.sender_valid?(bob.diaspora_id)
}.to raise_error Entities::Relayable::SignatureVerificationFailed, "wrong author_signature for #{invalid_msg}"
end
it "raises NotFetchable if the parent Conversation can not be found" do
expect_callback(:fetch_related_entity, "Conversation", entity.conversation_guid).and_return(nil)
expect {
entity.sender_valid?(bob.diaspora_id)
}.to raise_error Federation::Fetcher::NotFetchable
end
end
context "relayable signature verification" do
it "does not verify the signature" do
data.merge!(author_signature: "aa", parent_author_signature: "bb")
xml = Entities::Message.new(data).to_xml
expect {
Entities::Message.from_xml(xml)
}.not_to raise_error
end
end
describe ".from_xml" do
it "adds a nil parent" do
xml = Entities::Message.new(data).to_xml
parsed = Entities::Message.from_xml(xml)
expect(parsed.parent).to be_nil
end
it "uses the parent_guid from the parsed xml" do
xml = Entities::Message.new(data).to_xml
parsed = Entities::Message.from_xml(xml)
expect(parsed.parent_guid).to eq(data[:parent_guid])
end
it "uses nil for parent_guid if not in the xml" do
xml = <<-XML
<message>
<author>#{data[:author]}</author>
<guid>#{data[:guid]}</guid>
<text>#{data[:text]}</text>
<created_at>#{data[:created_at]}</created_at>
<created_at>#{data[:created_at].utc.iso8601}</created_at>
<conversation_guid>#{data[:conversation_guid]}</conversation_guid>
</message>
XML
parsed = Entities::Message.from_xml(Nokogiri::XML::Document.parse(xml).root)
expect(parsed.parent_guid).to be_nil
end
end
let(:string) { "Message:#{data[:guid]}:#{data[:conversation_guid]}" }
it_behaves_like "an Entity subclass"
it_behaves_like "an XML Entity"
end
end

View file

@ -1,25 +1,21 @@
module DiasporaFederation
describe Entities::Participation do
let(:parent) { Fabricate(:post, author: bob) }
let(:parent_entity) { Fabricate(:related_entity, author: bob.diaspora_id) }
let(:data) {
Fabricate.attributes_for(
:participation_entity,
author: alice.diaspora_id,
parent_guid: parent.guid,
parent_type: parent.entity_type,
parent: parent_entity
).tap {|hash| add_signatures(hash) }
parent_type: parent.entity_type
)
}
let(:xml) { <<-XML }
<participation>
<author>#{data[:author]}</author>
<guid>#{data[:guid]}</guid>
<target_type>#{parent.entity_type}</target_type>
<parent_guid>#{parent.guid}</parent_guid>
<diaspora_handle>#{data[:author]}</diaspora_handle>
<author_signature>#{data[:author_signature]}</author_signature>
<parent_author_signature>#{data[:parent_author_signature]}</parent_author_signature>
<parent_type>#{parent.entity_type}</parent_type>
</participation>
XML
@ -30,16 +26,8 @@ XML
"author": "#{data[:author]}",
"guid": "#{data[:guid]}",
"parent_guid": "#{parent.guid}",
"author_signature": "#{data[:author_signature]}",
"parent_author_signature": "#{data[:parent_author_signature]}",
"parent_type": "#{parent.entity_type}"
},
"property_order": [
"guid",
"parent_type",
"parent_guid",
"author"
]
}
}
JSON
@ -47,36 +35,11 @@ JSON
it_behaves_like "an Entity subclass"
it_behaves_like "an XML Entity", [:parent]
it_behaves_like "an XML Entity"
it_behaves_like "a JSON Entity"
it_behaves_like "a relayable Entity"
it_behaves_like "a relayable JSON entity"
describe "#sender_valid?" do
let(:entity) { Entities::Participation.new(data) }
it "allows the author" do
expect(entity.sender_valid?(alice.diaspora_id)).to be_truthy
end
it "does not allow the parent author" do
expect(entity.sender_valid?(bob.diaspora_id)).to be_falsey
end
end
context "parse xml" do
it "does not verify the signature" do
data.merge!(author_signature: "aa", parent_author_signature: "bb")
xml = Entities::Participation.new(data).to_xml
expect {
Entities::Participation.from_xml(xml)
}.not_to raise_error
end
describe "#validate_parent" do
let(:participation) {
allow(DiasporaFederation.callbacks).to receive(:trigger).and_call_original

View file

@ -5,10 +5,10 @@ module DiasporaFederation
let(:xml) { <<-XML }
<person>
<guid>#{data[:guid]}</guid>
<diaspora_handle>#{data[:author]}</diaspora_handle>
<author>#{data[:author]}</author>
<url>#{data[:url]}</url>
<profile>
<diaspora_handle>#{data[:profile].author}</diaspora_handle>
<author>#{data[:profile].author}</author>
<first_name>#{data[:profile].first_name}</first_name>
<last_name/>
<image_url>#{data[:profile].image_url}</image_url>

View file

@ -5,7 +5,7 @@ module DiasporaFederation
let(:xml) { <<-XML }
<photo>
<guid>#{data[:guid]}</guid>
<diaspora_handle>#{data[:author]}</diaspora_handle>
<author>#{data[:author]}</author>
<public>#{data[:public]}</public>
<created_at>#{data[:created_at].utc.iso8601}</created_at>
<remote_photo_path>#{data[:remote_photo_path]}</remote_photo_path>
@ -58,7 +58,7 @@ JSON
</photo>
XML
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(minimal_xml).root)
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML(minimal_xml).root)
expect(parsed_instance.public).to be_falsey
expect(parsed_instance.text).to be_nil
end

View file

@ -13,9 +13,9 @@ module DiasporaFederation
let(:xml) { <<-XML }
<poll_participation>
<author>#{data[:author]}</author>
<guid>#{data[:guid]}</guid>
<parent_guid>#{parent.guid}</parent_guid>
<diaspora_handle>#{data[:author]}</diaspora_handle>
<poll_answer_guid>#{data[:poll_answer_guid]}</poll_answer_guid>
<author_signature>#{data[:author_signature]}</author_signature>
<parent_author_signature>#{data[:parent_author_signature]}</parent_author_signature>
@ -34,9 +34,9 @@ XML
"poll_answer_guid": "#{data[:poll_answer_guid]}"
},
"property_order": [
"author",
"guid",
"parent_guid",
"author",
"poll_answer_guid"
]
}

View file

@ -4,7 +4,7 @@ module DiasporaFederation
let(:xml) { <<-XML }
<profile>
<diaspora_handle>#{data[:author]}</diaspora_handle>
<author>#{data[:author]}</author>
<first_name>#{data[:first_name]}</first_name>
<last_name/>
<image_url>#{data[:image_url]}</image_url>
@ -59,7 +59,7 @@ JSON
</profile>
XML
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(minimal_xml).root)
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML(minimal_xml).root)
expect(parsed_instance.first_name).to be_nil
expect(parsed_instance.last_name).to be_nil
expect(parsed_instance.image_url).to be_nil

View file

@ -8,22 +8,11 @@ module DiasporaFederation
parent: Fabricate(:related_entity, author: alice.diaspora_id)
)
}
let(:data) {
Fabricate(
:relayable_retraction_entity,
author: alice.diaspora_id,
target_guid: target.guid,
target_type: target.entity_type,
target: target_entity
).send(:enriched_properties).tap do |data|
data[:target_author_signature] = ""
data[:target] = target_entity
end
}
let(:data) { {author: alice.diaspora_id, target_guid: target.guid, target_type: target.entity_type} }
let(:xml) { <<-XML }
<relayable_retraction>
<parent_author_signature>#{data[:parent_author_signature]}</parent_author_signature>
<parent_author_signature/>
<target_guid>#{data[:target_guid]}</target_guid>
<target_type>#{data[:target_type]}</target_type>
<sender_handle>#{data[:author]}</sender_handle>
@ -31,94 +20,24 @@ module DiasporaFederation
</relayable_retraction>
XML
let(:string) { "RelayableRetraction:#{data[:target_type]}:#{data[:target_guid]}" }
it_behaves_like "an Entity subclass"
it_behaves_like "an XML Entity", %i(parent_author_signature target_author_signature target)
it_behaves_like "a retraction"
describe "#to_xml" do
let(:author_pkey) { OpenSSL::PKey::RSA.generate(1024) }
let(:hash) { Fabricate.attributes_for(:relayable_retraction_entity) }
it "updates author signature when it was nil and key was supplied and author is not parent author" do
parent = Fabricate(:related_entity, author: bob.diaspora_id)
hash[:target] = Fabricate(:related_entity, author: hash[:author], parent: parent)
expect_callback(:fetch_private_key, hash[:author]).and_return(author_pkey)
signed_string = "#{hash[:target_guid]};#{hash[:target_type]}"
xml = Entities::RelayableRetraction.new(hash).to_xml
signature = Base64.decode64(xml.at_xpath("target_author_signature").text)
expect(author_pkey.verify(OpenSSL::Digest::SHA256.new, signature, signed_string)).to be_truthy
end
it "sets parent author signature when author is parent author" do
parent = Fabricate(:related_entity, author: hash[:author])
hash[:target] = Fabricate(:related_entity, author: hash[:author], parent: parent)
expect_callback(:fetch_private_key, hash[:author]).and_return(author_pkey)
signed_string = "#{hash[:target_guid]};#{hash[:target_type]}"
xml = Entities::RelayableRetraction.new(hash).to_xml
signature = Base64.decode64(xml.at_xpath("parent_author_signature").text)
expect(author_pkey.verify(OpenSSL::Digest::SHA256.new, signature, signed_string)).to be_truthy
end
it "updates parent author signature when it was nil, key was supplied and sender is author of the parent" do
parent = Fabricate(:related_entity, author: hash[:author])
hash[:target] = Fabricate(:related_entity, author: bob.diaspora_id, parent: parent)
expect_callback(:fetch_private_key, hash[:author]).and_return(author_pkey)
signed_string = "#{hash[:target_guid]};#{hash[:target_type]}"
xml = Entities::RelayableRetraction.new(hash).to_xml
signature = Base64.decode64(xml.at_xpath("parent_author_signature").text)
expect(author_pkey.verify(OpenSSL::Digest::SHA256.new, signature, signed_string)).to be_truthy
end
it "doesn't change signatures if they are already set" do
hash.merge!(target_author_signature: "aa", parent_author_signature: "bb")
xml = Entities::RelayableRetraction.new(hash).to_xml
expect(xml.at_xpath("target_author_signature").text).to eq("aa")
expect(xml.at_xpath("parent_author_signature").text).to eq("bb")
end
it "doesn't change signatures if keys weren't supplied" do
expect_callback(:fetch_private_key, hash[:author]).and_return(nil)
xml = Entities::RelayableRetraction.new(hash).to_xml
expect(xml.at_xpath("target_author_signature").text).to eq("")
expect(xml.at_xpath("parent_author_signature").text).to eq("")
end
end
describe "#to_retraction" do
it "copies the attributes to a Retraction" do
relayable_retraction = Fabricate(:relayable_retraction_entity)
retraction = relayable_retraction.to_retraction
expect(retraction).to be_a(Entities::Retraction)
expect(retraction.author).to eq(relayable_retraction.author)
expect(retraction.target_guid).to eq(relayable_retraction.target_guid)
expect(retraction.target_type).to eq(relayable_retraction.target_type)
describe "#initialize" do
it "raises because it is not supported anymore" do
expect {
Entities::RelayableRetraction.new(data)
}.to raise_error RuntimeError,
"Sending RelayableRetraction is not supported anymore! Use Retraction instead!"
end
end
context "parse retraction" do
it "parses the xml as a retraction" do
retraction = Entities::RelayableRetraction.from_xml(Nokogiri::XML::Document.parse(xml).root)
expect(Entities::Retraction).to receive(:fetch_target).and_return(target_entity)
retraction = Entities::RelayableRetraction.from_xml(Nokogiri::XML(xml).root)
expect(retraction).to be_a(Entities::Retraction)
expect(retraction.author).to eq(data[:author])
expect(retraction.target_guid).to eq(data[:target_guid])
expect(retraction.target_type).to eq(data[:target_type])
expect(retraction.target).to eq(target_entity)
end
end
end

View file

@ -13,61 +13,88 @@ module DiasporaFederation
let(:hash) { {guid: guid, author: author, parent_guid: parent_guid, parent: local_parent, property: property} }
let(:hash_with_fake_signatures) { hash.merge!(author_signature: "aa", parent_author_signature: "bb") }
let(:legacy_signature_data) { "#{guid};#{author};#{property};#{parent_guid}" }
let(:signature_order) { %i(author guid parent_guid property) }
let(:signature_data) { "#{author};#{guid};#{parent_guid};#{property}" }
describe "#initialize" do
it "filters signatures from order" do
xml_order = [:author, :guid, :parent_guid, :property, "new_property", :author_signature]
signature_order = [:author, :guid, :parent_guid, :property, "new_property", :author_signature]
expect(Entities::SomeRelayable.new(hash, xml_order).xml_order)
expect(Entities::SomeRelayable.new(hash, signature_order).signature_order)
.to eq([:author, :guid, :parent_guid, :property, "new_property"])
end
end
describe "#verify_signatures" do
it "doesn't raise anything if correct signatures with legacy-string were passed" do
hash[:author_signature] = sign_with_key(author_pkey, legacy_signature_data)
hash[:parent_author_signature] = sign_with_key(parent_pkey, legacy_signature_data)
it "doesn't raise anything if correct signatures were passed" do
hash[:author_signature] = sign_with_key(author_pkey, signature_data)
hash[:parent_author_signature] = sign_with_key(parent_pkey, signature_data)
hash[:parent] = remote_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
expect { Entities::SomeRelayable.new(hash).verify_signatures }.not_to raise_error
expect { Entities::SomeRelayable.new(hash, signature_order).verify_signatures }.not_to raise_error
end
it "doesn't raise anything if correct signatures with new property were passed" do
signature_order = [:author, :guid, :parent_guid, :property, "new_property"]
signature_data_with_new_property = "#{author};#{guid};#{parent_guid};#{property};#{new_property}"
hash[:author_signature] = sign_with_key(author_pkey, signature_data_with_new_property)
hash[:parent_author_signature] = sign_with_key(parent_pkey, signature_data_with_new_property)
hash[:parent] = remote_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
expect {
Entities::SomeRelayable.new(hash, signature_order, "new_property" => new_property).verify_signatures
}.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::SomeRelayable.new(hash).verify_signatures
Entities::SomeRelayable.new(hash, signature_order).verify_signatures
}.to raise_error Entities::Relayable::PublicKeyNotFound
end
it "raises when bad author signature was passed" do
it "raises when no author signature was passed" do
hash[:author_signature] = nil
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect {
Entities::SomeRelayable.new(hash).verify_signatures
Entities::SomeRelayable.new(hash, signature_order).verify_signatures
}.to raise_error Entities::Relayable::SignatureVerificationFailed
end
it "raises when bad author signature was passed" do
hash[:author_signature] = sign_with_key(author_pkey, "bad signed string")
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect {
Entities::SomeRelayable.new(hash, signature_order).verify_signatures
}.to raise_error Entities::Relayable::SignatureVerificationFailed
end
it "raises when no public key for parent author was fetched" do
hash[:author_signature] = sign_with_key(author_pkey, legacy_signature_data)
hash[:author_signature] = sign_with_key(author_pkey, signature_data)
hash[:parent] = remote_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(nil)
expect {
Entities::SomeRelayable.new(hash).verify_signatures
Entities::SomeRelayable.new(hash, signature_order).verify_signatures
}.to raise_error Entities::Relayable::PublicKeyNotFound
end
it "raises when bad parent author signature was passed" do
hash[:author_signature] = sign_with_key(author_pkey, legacy_signature_data)
it "raises when no parent author signature was passed" do
hash[:author_signature] = sign_with_key(author_pkey, signature_data)
hash[:parent_author_signature] = nil
hash[:parent] = remote_parent
@ -75,68 +102,38 @@ module DiasporaFederation
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
expect {
Entities::SomeRelayable.new(hash).verify_signatures
Entities::SomeRelayable.new(hash, signature_order).verify_signatures
}.to raise_error Entities::Relayable::SignatureVerificationFailed
end
it "raises when bad parent author signature was passed" do
hash[:author_signature] = sign_with_key(author_pkey, signature_data)
hash[:parent_author_signature] = sign_with_key(parent_pkey, "bad signed string")
hash[:parent] = remote_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
expect {
Entities::SomeRelayable.new(hash, signature_order).verify_signatures
}.to raise_error Entities::Relayable::SignatureVerificationFailed
end
it "doesn't raise if parent_author_signature isn't set but we're on upstream federation" do
hash[:author_signature] = sign_with_key(author_pkey, legacy_signature_data)
hash[:author_signature] = sign_with_key(author_pkey, signature_data)
hash[:parent_author_signature] = nil
hash[:parent] = local_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect { Entities::SomeRelayable.new(hash).verify_signatures }.not_to raise_error
end
context "new signatures" do
it "doesn't raise anything if correct signatures with new order were passed" do
xml_order = %i(author guid parent_guid property)
signature_data = "#{author};#{guid};#{parent_guid};#{property}"
hash[:author_signature] = sign_with_key(author_pkey, signature_data)
hash[:parent_author_signature] = sign_with_key(parent_pkey, signature_data)
hash[:parent] = remote_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
expect { Entities::SomeRelayable.new(hash, xml_order).verify_signatures }.not_to raise_error
end
it "doesn't raise anything if correct signatures with new property were passed" do
xml_order = [:author, :guid, :parent_guid, :property, "new_property"]
signature_data_with_new_property = "#{author};#{guid};#{parent_guid};#{property};#{new_property}"
hash[:author_signature] = sign_with_key(author_pkey, signature_data_with_new_property)
hash[:parent_author_signature] = sign_with_key(parent_pkey, signature_data_with_new_property)
hash[:parent] = remote_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
expect {
Entities::SomeRelayable.new(hash, xml_order, "new_property" => new_property).verify_signatures
}.not_to raise_error
end
it "raises with legacy-signatures and with new property and order" do
hash[:author_signature] = sign_with_key(author_pkey, legacy_signature_data)
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
xml_order = [:author, :guid, :parent_guid, :property, "new_property"]
expect {
Entities::SomeRelayable.new(hash, xml_order, "new_property" => new_property).verify_signatures
}.to raise_error Entities::Relayable::SignatureVerificationFailed
end
expect { Entities::SomeRelayable.new(hash, signature_order).verify_signatures }.not_to raise_error
end
end
describe "#to_xml" do
let(:expected_xml) { <<-XML }
<some_relayable>
<diaspora_handle>#{author}</diaspora_handle>
<author>#{author}</author>
<guid>#{guid}</guid>
<parent_guid>#{parent_guid}</parent_guid>
<property>#{property}</property>
@ -147,23 +144,27 @@ module DiasporaFederation
XML
it "adds new unknown xml elements to the xml again" do
xml_order = [:author, :guid, :parent_guid, :property, "new_property"]
xml = Entities::SomeRelayable.new(hash_with_fake_signatures, xml_order, "new_property" => new_property).to_xml
signature_order = [:author, :guid, :parent_guid, :property, "new_property"]
xml = Entities::SomeRelayable.new(
hash_with_fake_signatures, signature_order, "new_property" => new_property
).to_xml
expect(xml.to_s.strip).to eq(expected_xml.strip)
end
it "converts strings in xml_order to symbol if needed" do
xml_order = %w(author guid parent_guid property new_property)
xml = Entities::SomeRelayable.new(hash_with_fake_signatures, xml_order, "new_property" => new_property).to_xml
it "accepts string names of known properties in signature_order" do
signature_order = %w(author guid parent_guid property new_property)
xml = Entities::SomeRelayable.new(
hash_with_fake_signatures, signature_order, "new_property" => new_property
).to_xml
expect(xml.to_s.strip).to eq(expected_xml.strip)
end
it "adds missing properties from xml_order to xml" do
xml_order = [:author, :guid, :parent_guid, :property, "new_property"]
it "adds missing properties from signature_order to xml" do
signature_order = [:author, :guid, :parent_guid, :property, "new_property"]
xml = Entities::SomeRelayable.new(hash_with_fake_signatures, xml_order).to_xml
xml = Entities::SomeRelayable.new(hash_with_fake_signatures, signature_order).to_xml
expect(xml.at_xpath("new_property").text).to be_empty
end
@ -177,18 +178,18 @@ XML
author_signature = xml.at_xpath("author_signature").text
parent_author_signature = xml.at_xpath("parent_author_signature").text
expect(verify_signature(author_pkey, author_signature, legacy_signature_data)).to be_truthy
expect(verify_signature(parent_pkey, parent_author_signature, legacy_signature_data)).to be_truthy
expect(verify_signature(author_pkey, author_signature, signature_data)).to be_truthy
expect(verify_signature(parent_pkey, parent_author_signature, signature_data)).to be_truthy
end
it "computes correct signatures for the entity with new unknown xml elements" do
expect_callback(:fetch_private_key, author).and_return(author_pkey)
expect_callback(:fetch_private_key, local_parent.author).and_return(parent_pkey)
xml_order = [:author, :guid, :parent_guid, "new_property", :property]
signature_order = [:author, :guid, :parent_guid, "new_property", :property]
signature_data_with_new_property = "#{author};#{guid};#{parent_guid};#{new_property};#{property}"
xml = Entities::SomeRelayable.new(hash, xml_order, "new_property" => new_property).to_xml
xml = Entities::SomeRelayable.new(hash, signature_order, "new_property" => new_property).to_xml
author_signature = xml.at_xpath("author_signature").text
parent_author_signature = xml.at_xpath("parent_author_signature").text
@ -244,7 +245,7 @@ XML
XML
it "doesn't drop unknown properties" do
entity = Entities::SomeRelayable.from_xml(Nokogiri::XML::Document.parse(new_xml).root)
entity = Entities::SomeRelayable.from_xml(Nokogiri::XML(new_xml).root)
expect(entity).to be_an_instance_of Entities::SomeRelayable
expect(entity.property).to eq(property)
@ -254,14 +255,14 @@ XML
end
it "hand over the order in the xml to the instance without signatures" do
entity = Entities::SomeRelayable.from_xml(Nokogiri::XML::Document.parse(new_xml).root)
entity = Entities::SomeRelayable.from_xml(Nokogiri::XML(new_xml).root)
expect(entity.xml_order).to eq([:author, :guid, :parent_guid, "new_property", :property])
expect(entity.signature_order).to eq([:author, :guid, :parent_guid, "new_property", :property])
end
it "creates Entity with empty 'additional_data' if the xml has only known properties" do
hash[:author_signature] = sign_with_key(author_pkey, legacy_signature_data)
hash[:parent_author_signature] = sign_with_key(parent_pkey, legacy_signature_data)
hash[:author_signature] = sign_with_key(author_pkey, signature_data)
hash[:parent_author_signature] = sign_with_key(parent_pkey, signature_data)
xml = Entities::SomeRelayable.new(hash).to_xml
@ -283,7 +284,7 @@ XML
XML
expect {
Entities::SomeRelayable.from_xml(Nokogiri::XML::Document.parse(broken_xml).root)
Entities::SomeRelayable.from_xml(Nokogiri::XML(broken_xml).root)
}.to raise_error Entity::ValidationError,
"invalid DiasporaFederation::Entities::SomeRelayable! missing 'parent_guid'."
end
@ -300,11 +301,11 @@ XML
expect(json).to include_json(property_order: property_order.map(&:to_s))
end
it "uses legacy order for filling property_order when no xml_order supplied" do
it "uses property order for filling property_order when no signature_order supplied" do
entity = entity_class.new(hash_with_fake_signatures)
expect(
entity.to_json.to_json
).to include_json(property_order: entity_class::LEGACY_SIGNATURE_ORDER.map(&:to_s))
).to include_json(property_order: %w(author guid parent_guid property))
end
it "adds new unknown elements to the json again" do
@ -411,7 +412,7 @@ XML
it "hands over the order in the data to the instance without signatures" do
entity = Entities::SomeRelayable.from_hash(entity_data, property_order)
expect(entity.xml_order).to eq(%w(author guid parent_guid new_property property))
expect(entity.signature_order).to eq([:author, :guid, :parent_guid, "new_property", :property])
end
it "calls a constructor of the entity of the appropriate type" do
@ -433,15 +434,15 @@ XML
end
it "creates Entity with empty 'additional_data' if it has only known properties" do
property_order = %w(guid author property parent_guid)
property_order = %w(author guid parent_guid property)
entity_data = {
guid: guid,
author: author,
property: property,
parent_guid: parent_guid,
author_signature: sign_with_key(author_pkey, legacy_signature_data),
parent_author_signature: sign_with_key(parent_pkey, legacy_signature_data)
author_signature: sign_with_key(author_pkey, signature_data),
parent_author_signature: sign_with_key(parent_pkey, signature_data)
}
entity = Entities::SomeRelayable.from_hash(entity_data, property_order)

View file

@ -1,6 +1,6 @@
module DiasporaFederation
describe Entities::Request do
let(:data) { Fabricate.attributes_for(:request_entity) }
let(:data) { {author: Fabricate.sequence(:diaspora_id), recipient: Fabricate.sequence(:diaspora_id)} }
let(:xml) { <<-XML }
<request>
@ -9,29 +9,22 @@ module DiasporaFederation
</request>
XML
let(:string) { "Request:#{data[:author]}:#{data[:recipient]}" }
it_behaves_like "an Entity subclass"
it_behaves_like "an XML Entity"
describe "#to_contact" do
it "copies the attributes to a Contact" do
request = Fabricate(:request_entity)
contact = request.to_contact
expect(contact).to be_a(Entities::Contact)
expect(contact.author).to eq(request.author)
expect(contact.recipient).to eq(request.recipient)
expect(contact.following).to be_truthy
expect(contact.sharing).to be_truthy
describe "#initialize" do
it "raises because it is not supported anymore" do
expect {
Entities::Request.new(data)
}.to raise_error RuntimeError, "Sending Request is not supported anymore! Use Contact instead!"
end
end
context "parse contact" do
it "parses the xml as a contact" do
contact = Entities::Request.from_xml(Nokogiri::XML::Document.parse(xml).root)
contact = Entities::Request.from_xml(Nokogiri::XML(xml).root)
expect(contact).to be_a(Entities::Contact)
expect(contact.author).to eq(data[:author])
expect(contact.recipient).to eq(data[:recipient])
expect(contact.following).to be_truthy
expect(contact.sharing).to be_truthy
end
end
end

View file

@ -5,11 +5,11 @@ module DiasporaFederation
let(:xml) { <<-XML }
<reshare>
<diaspora_handle>#{data[:author]}</diaspora_handle>
<author>#{data[:author]}</author>
<guid>#{data[:guid]}</guid>
<created_at>#{data[:created_at].utc.iso8601}</created_at>
<provider_display_name>#{data[:provider_display_name]}</provider_display_name>
<root_diaspora_id>#{data[:root_author]}</root_diaspora_id>
<root_author>#{data[:root_author]}</root_author>
<root_guid>#{data[:root_guid]}</root_guid>
<public>#{data[:public]}</public>
</reshare>
@ -50,7 +50,7 @@ JSON
</reshare>
XML
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(minimal_xml).root)
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML(minimal_xml).root)
expect(parsed_instance.public).to be_truthy
expect(parsed_instance.provider_display_name).to be_nil
end
@ -61,7 +61,7 @@ XML
expect_callback(:fetch_related_entity, "Post", data[:root_guid]).and_return(nil)
expect(Federation::Fetcher).to receive(:fetch_public).with(data[:root_author], "Post", data[:root_guid])
Entities::Reshare.from_xml(Nokogiri::XML::Document.parse(xml).root)
Entities::Reshare.from_xml(Nokogiri::XML(xml).root)
end
end
end

View file

@ -13,9 +13,9 @@ module DiasporaFederation
let(:xml) { <<-XML }
<retraction>
<diaspora_handle>#{data[:author]}</diaspora_handle>
<post_guid>#{data[:target_guid]}</post_guid>
<type>#{data[:target_type]}</type>
<author>#{data[:author]}</author>
<target_guid>#{data[:target_guid]}</target_guid>
<target_type>#{data[:target_type]}</target_type>
</retraction>
XML
@ -25,7 +25,16 @@ XML
it_behaves_like "an XML Entity"
it_behaves_like "a retraction"
context "receive with no target found" do
it "raises when no target is found" do
unknown_guid = Fabricate.sequence(:guid)
retraction = Entities::Retraction.new(data.merge(target_guid: unknown_guid))
expect {
described_class.from_xml(retraction.to_xml)
}.to raise_error DiasporaFederation::Entities::Retraction::TargetNotFound,
"not found: #{data[:target_type]}:#{unknown_guid}"
end
end
describe "#sender_valid?" do
context "unrelayable target" do

View file

@ -2,80 +2,35 @@ module DiasporaFederation
describe Entities::SignedRetraction do
let(:target) { Fabricate(:post, author: alice) }
let(:target_entity) { Fabricate(:related_entity, author: alice.diaspora_id) }
let(:data) {
Fabricate(
:signed_retraction_entity,
author: alice.diaspora_id,
target_guid: target.guid,
target_type: target.entity_type,
target: target_entity
).send(:enriched_properties).merge(target: target_entity)
}
let(:data) { {author: alice.diaspora_id, target_guid: target.guid, target_type: target.entity_type} }
let(:xml) { <<-XML }
<signed_retraction>
<target_guid>#{data[:target_guid]}</target_guid>
<target_type>#{data[:target_type]}</target_type>
<sender_handle>#{data[:author]}</sender_handle>
<target_author_signature>#{data[:target_author_signature]}</target_author_signature>
<target_author_signature/>
</signed_retraction>
XML
let(:string) { "SignedRetraction:#{data[:target_type]}:#{data[:target_guid]}" }
it_behaves_like "an Entity subclass"
it_behaves_like "an XML Entity", [:target_author_signature]
it_behaves_like "a retraction"
describe "#to_xml" do
let(:author_pkey) { OpenSSL::PKey::RSA.generate(1024) }
let(:hash) { Fabricate.attributes_for(:signed_retraction_entity) }
it "updates author signature when it was nil and key was supplied" do
expect_callback(:fetch_private_key, hash[:author]).and_return(author_pkey)
signed_string = "#{hash[:target_guid]};#{hash[:target_type]}"
xml = Entities::SignedRetraction.new(hash).to_xml
signature = Base64.decode64(xml.at_xpath("target_author_signature").text)
expect(author_pkey.verify(OpenSSL::Digest::SHA256.new, signature, signed_string)).to be_truthy
end
it "doesn't change signature if it is already set" do
hash[:target_author_signature] = "aa"
xml = Entities::SignedRetraction.new(hash).to_xml
expect(xml.at_xpath("target_author_signature").text).to eq("aa")
end
it "doesn't change signature if a key wasn't supplied" do
expect_callback(:fetch_private_key, hash[:author]).and_return(nil)
xml = Entities::SignedRetraction.new(hash).to_xml
expect(xml.at_xpath("target_author_signature").text).to eq("")
end
end
describe "#to_retraction" do
it "copies the attributes to a Retraction" do
signed_retraction = Fabricate(:signed_retraction_entity)
retraction = signed_retraction.to_retraction
expect(retraction).to be_a(Entities::Retraction)
expect(retraction.author).to eq(signed_retraction.author)
expect(retraction.target_guid).to eq(signed_retraction.target_guid)
expect(retraction.target_type).to eq(signed_retraction.target_type)
describe "#initialize" do
it "raises because it is not supported anymore" do
expect {
Entities::SignedRetraction.new(data)
}.to raise_error RuntimeError,
"Sending SignedRetraction is not supported anymore! Use Retraction instead!"
end
end
context "parse retraction" do
it "parses the xml as a retraction" do
retraction = Entities::SignedRetraction.from_xml(Nokogiri::XML::Document.parse(xml).root)
expect(Entities::Retraction).to receive(:fetch_target).and_return(target_entity)
retraction = Entities::SignedRetraction.from_xml(Nokogiri::XML(xml).root)
expect(retraction).to be_a(Entities::Retraction)
expect(retraction.author).to eq(data[:author])
expect(retraction.target_guid).to eq(data[:target_guid])
expect(retraction.target_type).to eq(data[:target_type])
expect(retraction.target).to eq(target_entity)
end
end
end

View file

@ -16,14 +16,14 @@ module DiasporaFederation
let(:xml) { <<-XML }
<status_message>
<diaspora_handle>#{data[:author]}</diaspora_handle>
<author>#{data[:author]}</author>
<guid>#{data[:guid]}</guid>
<created_at>#{data[:created_at].utc.iso8601}</created_at>
<provider_display_name>#{data[:provider_display_name]}</provider_display_name>
<raw_message>#{data[:text]}</raw_message>
<text>#{data[:text]}</text>
<photo>
<guid>#{photo1.guid}</guid>
<diaspora_handle>#{photo1.author}</diaspora_handle>
<author>#{photo1.author}</author>
<public>#{photo1.public}</public>
<created_at>#{photo1.created_at.utc.iso8601}</created_at>
<remote_photo_path>#{photo1.remote_photo_path}</remote_photo_path>
@ -35,7 +35,7 @@ module DiasporaFederation
</photo>
<photo>
<guid>#{photo2.guid}</guid>
<diaspora_handle>#{photo2.author}</diaspora_handle>
<author>#{photo2.author}</author>
<public>#{photo2.public}</public>
<created_at>#{photo2.created_at.utc.iso8601}</created_at>
<remote_photo_path>#{photo2.remote_photo_path}</remote_photo_path>
@ -129,7 +129,7 @@ JSON
</status_message>
XML
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(minimal_xml).root)
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML(minimal_xml).root)
expect(parsed_instance.photos).to eq([])
expect(parsed_instance.location).to be_nil
expect(parsed_instance.poll).to be_nil

View file

@ -99,7 +99,7 @@ module DiasporaFederation
it "replaces invalid XML characters" do
entity = Entities::TestEntity.new(test: "asdfasdf asdf💩asdf\nasdf")
xml = entity.to_xml.to_xml
parsed = Entities::TestEntity.from_xml(Nokogiri::XML::Document.parse(xml).root).test
parsed = Entities::TestEntity.from_xml(Nokogiri::XML(xml).root).test
expect(parsed).to eq("asdf<EFBFBD>asdf asdf💩asdf\nasdf")
end
end
@ -127,7 +127,7 @@ module DiasporaFederation
test2: "qwer",
test3: true
)
Entities::TestDefaultEntity.from_xml(Nokogiri::XML::Document.parse(<<-XML).root)
Entities::TestDefaultEntity.from_xml(Nokogiri::XML(<<-XML).root)
<test_default_entity>
<test1>asdf</test1>
<test2>qwer</qwer2>
@ -140,11 +140,11 @@ XML
arguments = [{arg1: "value"}]
expect_any_instance_of(DiasporaFederation::Parsers::XmlParser).to receive(:parse).and_return(arguments)
expect(Entities::TestDefaultEntity).to receive(:from_hash).with(*arguments)
Entities::TestDefaultEntity.from_xml(Nokogiri::XML::Document.parse("<dummy/>").root)
Entities::TestDefaultEntity.from_xml(Nokogiri::XML("<dummy/>").root)
end
it "passes input parameter directly to .parse method of the parser" do
root = Nokogiri::XML::Document.parse("<dummy/>").root
root = Nokogiri::XML("<dummy/>").root
expect_any_instance_of(DiasporaFederation::Parsers::XmlParser).to receive(:parse).with(root)
.and_return([{test1: "2", test2: "1"}])
Entities::TestDefaultEntity.from_xml(root)
@ -444,7 +444,7 @@ JSON
</test_nested_entity>
XML
entity = Entities::TestNestedEntity.from_xml(Nokogiri::XML::Document.parse(xml).root)
entity = Entities::TestNestedEntity.from_xml(Nokogiri::XML(xml).root)
expect(entity.asdf).to eq("FDSA")
expect(entity.test).to be_nil
@ -455,15 +455,6 @@ XML
context "xml_name" do
let(:hash) { {test: "test", qwer: "qwer"} }
it "uses xml_name for the #to_xml" do
entity = Entities::TestEntityWithXmlName.new(hash)
xml_children = entity.to_xml.children
expect(xml_children).to have_exactly(2).items
xml_children.each do |node|
expect(%w(test asdf)).to include(node.name)
end
end
it "should not use the xml_name for the #to_h" do
entity = Entities::TestEntityWithXmlName.new(hash)
expect(entity.to_h).to eq(hash)

View file

@ -24,7 +24,7 @@ module DiasporaFederation
it "parses the entity with legacy slap receiver" do
expect_callback(:fetch_public_key, post.author).and_return(sender_key)
data = DiasporaFederation::Salmon::Slap.generate_xml(post.author, sender_key, post)
data = generate_legacy_salmon_slap(post, post.author, sender_key)
expect_callback(:receive_entity, kind_of(Entities::StatusMessage), post.author, nil) do |_, entity|
expect(entity.guid).to eq(post.guid)
@ -65,8 +65,7 @@ module DiasporaFederation
it "parses the entity with legacy slap receiver" do
expect_callback(:fetch_public_key, post.author).and_return(sender_key)
data = DiasporaFederation::Salmon::EncryptedSlap.prepare(post.author, sender_key, post)
.generate_xml(recipient_key)
data = generate_legacy_encrypted_salmon_slap(post, post.author, sender_key, recipient_key)
expect_callback(:receive_entity, kind_of(Entities::StatusMessage), post.author, 1234) do |_, entity|
expect(entity.guid).to eq(post.guid)
@ -88,7 +87,7 @@ module DiasporaFederation
end
it "redirects exceptions from the receiver" do
invalid_magic_env = Nokogiri::XML::Document.parse("<xml/>").root
invalid_magic_env = Nokogiri::XML("<xml/>").root
data = Salmon::EncryptedMagicEnvelope.encrypt(invalid_magic_env, recipient_key.public_key)
expect {

View file

@ -3,6 +3,7 @@ module DiasporaFederation
let(:sender_id) { Fabricate.sequence(:diaspora_id) }
let(:obj_str) { "status_message@guid" }
let(:xml) { "<xml>post</xml>" }
let(:json) { "{\"aes_key\": \"...\", \"encrypted_magic_envelope\": \"...\"}" }
let(:url) { "http://example.org/receive/public" }
let(:url2) { "http://example.com/receive/public" }
@ -13,21 +14,45 @@ module DiasporaFederation
allow(Typhoeus::Hydra).to receive(:new).and_return(hydra)
end
describe "#insert_job" do
describe "#insert_magic_env_request" do
it "queues a request to hydra" do
expect(hydra).to receive(:queue).with(kind_of(Typhoeus::Request))
expect(Typhoeus::Request).to receive(:new).with(
url, Federation::Sender::HydraWrapper.hydra_opts.merge(body: {xml: xml})
url,
Federation::Sender::HydraWrapper.hydra_opts.merge(
body: xml, headers: Federation::Sender::HydraWrapper.xml_headers
)
).and_call_original
hydra_wrapper.insert_job(url, xml)
hydra_wrapper.insert_magic_env_request(url, xml)
end
it "queues multiple requests to hydra" do
expect(hydra).to receive(:queue).twice.with(kind_of(Typhoeus::Request))
hydra_wrapper.insert_job(url, xml)
hydra_wrapper.insert_job(url2, xml)
hydra_wrapper.insert_magic_env_request(url, xml)
hydra_wrapper.insert_magic_env_request(url2, xml)
end
end
describe "#insert_enc_magic_env_request" do
it "queues a request to hydra" do
expect(hydra).to receive(:queue).with(kind_of(Typhoeus::Request))
expect(Typhoeus::Request).to receive(:new).with(
url,
Federation::Sender::HydraWrapper.hydra_opts.merge(
body: json, headers: Federation::Sender::HydraWrapper.json_headers
)
).and_call_original
hydra_wrapper.insert_enc_magic_env_request(url, json)
end
it "queues multiple requests to hydra" do
expect(hydra).to receive(:queue).twice.with(kind_of(Typhoeus::Request))
hydra_wrapper.insert_enc_magic_env_request(url, json)
hydra_wrapper.insert_enc_magic_env_request(url2, json)
end
end
@ -54,8 +79,8 @@ module DiasporaFederation
before do
Typhoeus.stub(url).and_return(response)
Typhoeus.stub(url2).and_return(error_response)
hydra_wrapper.insert_job(url, xml)
hydra_wrapper.insert_job(url2, xml)
hydra_wrapper.insert_magic_env_request(url, xml)
hydra_wrapper.insert_magic_env_request(url2, xml)
end
before :all do
WebMock::HttpLibAdapters::TyphoeusAdapter.disable!
@ -88,7 +113,7 @@ module DiasporaFederation
return_code: :ok
)
Typhoeus.stub("http://example.net/receive/not_found").and_return(not_found)
hydra_wrapper.insert_job("http://example.net/receive/not_found", xml)
hydra_wrapper.insert_magic_env_request("http://example.net/receive/not_found", xml)
hydra_wrapper.send
end
@ -100,7 +125,7 @@ module DiasporaFederation
url3 = "http://example.net/receive/public"
Typhoeus.stub(url3).and_return(response)
hydra_wrapper.insert_job(url3, xml)
hydra_wrapper.insert_magic_env_request(url3, xml)
expect(hydra_wrapper.send).to eq([url2, url3])
end

View file

@ -13,8 +13,8 @@ module DiasporaFederation
let(:urls) { ["https://example.org/receive/public", "https://example.com/receive/public"] }
before do
expect(hydra_wrapper).to receive(:insert_job).with(urls.at(0), xml)
expect(hydra_wrapper).to receive(:insert_job).with(urls.at(1), xml)
expect(hydra_wrapper).to receive(:insert_magic_env_request).with(urls.at(0), xml)
expect(hydra_wrapper).to receive(:insert_magic_env_request).with(urls.at(1), xml)
end
it "returns empty array if send was successful" do
@ -34,14 +34,14 @@ module DiasporaFederation
describe ".private" do
let(:targets) {
{
"https://example.org/receive/user/guid" => "<xml>post</xml>",
"https://example.com/receive/user/guid" => "<xml>post2</xml>"
"https://example.org/receive/user/guid" => "{\"aes_key\": \"key1\", \"encrypted_magic_envelope\": \"...\"}",
"https://example.com/receive/user/guid" => "{\"aes_key\": \"key2\", \"encrypted_magic_envelope\": \"...\"}"
}
}
before do
targets.each do |url, xml|
expect(hydra_wrapper).to receive(:insert_job).with(url, xml)
targets.each do |url, json|
expect(hydra_wrapper).to receive(:insert_enc_magic_env_request).with(url, json)
end
end
@ -55,7 +55,9 @@ module DiasporaFederation
expect(hydra_wrapper).to receive(:send).and_return(["https://example.com/receive/user/guid"])
expect(Federation::Sender.private(sender_id, obj_str, targets))
.to eq("https://example.com/receive/user/guid" => "<xml>post2</xml>")
.to eq(
"https://example.com/receive/user/guid" => "{\"aes_key\": \"key2\", \"encrypted_magic_envelope\": \"...\"}"
)
end
end
end

View file

@ -4,7 +4,7 @@ module DiasporaFederation
let(:entity_class) { Entities::SomeRelayable }
let(:xml_parser) { Parsers::RelayableXmlParser.new(entity_class) }
it "passes order of the XML elements as a second argument in the returned list" do
xml_object = Nokogiri::XML::Document.parse(<<-XML).root
xml_object = Nokogiri::XML(<<-XML).root
<some_relayable>
<guid>im a guid</guid>
<property>value</property>

View file

@ -18,7 +18,7 @@ module DiasporaFederation
XML
expect {
xml_parser.parse(Nokogiri::XML::Document.parse(xml).root)
xml_parser.parse(Nokogiri::XML(xml).root)
}.to raise_error Parsers::BaseParser::InvalidRootNode,
"'unknown_entity' can't be parsed by DiasporaFederation::Entities::TestComplexEntity"
end
@ -39,7 +39,7 @@ XML
</test_entity_with_xml_name>
XML
parsed = Parsers::XmlParser.new(Entities::TestEntityWithXmlName).parse(Nokogiri::XML::Document.parse(xml).root)
parsed = Parsers::XmlParser.new(Entities::TestEntityWithXmlName).parse(Nokogiri::XML(xml).root)
expect(parsed[0][:test]).to eq("asdf")
expect(parsed[0][:qwer]).to eq("qwer")
@ -53,7 +53,7 @@ XML
</test_entity_with_xml_name>
XML
parsed = Parsers::XmlParser.new(Entities::TestEntityWithXmlName).parse(Nokogiri::XML::Document.parse(xml).root)
parsed = Parsers::XmlParser.new(Entities::TestEntityWithXmlName).parse(Nokogiri::XML(xml).root)
expect(parsed[0][:test]).to eq("asdf")
expect(parsed[0][:qwer]).to eq("qwer")
@ -68,7 +68,7 @@ XML
</test_default_entity>
XML
parsed = Parsers::XmlParser.new(Entities::TestDefaultEntity).parse(Nokogiri::XML::Document.parse(xml).root)
parsed = Parsers::XmlParser.new(Entities::TestDefaultEntity).parse(Nokogiri::XML(xml).root)
expect(parsed[0][:test1]).to eq("asdf")
expect(parsed[0][:test2]).to eq("qwer")
@ -82,7 +82,7 @@ XML
</test_entity_with_boolean>
XML
parsed = Parsers::XmlParser.new(Entities::TestEntityWithBoolean).parse(Nokogiri::XML::Document.parse(xml).root)
parsed = Parsers::XmlParser.new(Entities::TestEntityWithBoolean).parse(Nokogiri::XML(xml).root)
expect(parsed[0][:test]).to eq(false)
end
@ -95,7 +95,7 @@ XML
XML
parsed = Parsers::XmlParser.new(Entities::TestEntityWithBoolean).parse(
Nokogiri::XML::Document.parse(xml).root
Nokogiri::XML(xml).root
)
expect(parsed[0][:test]).to be_nil
end
@ -110,7 +110,7 @@ XML
XML
parsed = Parsers::XmlParser.new(Entities::TestEntityWithInteger).parse(
Nokogiri::XML::Document.parse(xml).root
Nokogiri::XML(xml).root
)
expect(parsed[0][:test]).to be_nil
end
@ -125,7 +125,7 @@ XML
XML
parsed = Parsers::XmlParser.new(Entities::TestEntityWithTimestamp).parse(
Nokogiri::XML::Document.parse(xml).root
Nokogiri::XML(xml).root
)
expect(parsed[0][:test]).to be_nil
end
@ -161,7 +161,7 @@ XML
</test_default_entity>
XML
parsed = Parsers::XmlParser.new(Entities::TestDefaultEntity).parse(Nokogiri::XML::Document.parse(xml).root)
parsed = Parsers::XmlParser.new(Entities::TestDefaultEntity).parse(Nokogiri::XML(xml).root)
expect(parsed[0]["test_new"]).to eq("new_value")
end
end

View file

@ -32,7 +32,7 @@ module DiasporaFederation
xml = Salmon::AES.decrypt(json["encrypted_magic_envelope"], key["key"], key["iv"])
expect(Nokogiri::XML::Document.parse(xml).root.to_xml).to eq(magic_env.to_xml)
expect(Nokogiri::XML(xml).to_xml).to eq(magic_env.to_xml)
end
end

View file

@ -4,137 +4,7 @@ module DiasporaFederation
let(:privkey) { OpenSSL::PKey::RSA.generate(512) } # use small key for speedy specs
let(:recipient_key) { OpenSSL::PKey::RSA.generate(1024) } # use small key for speedy specs
let(:payload) { Entities::TestEntity.new(test: "qwertzuiop") }
let(:slap_xml) { Salmon::EncryptedSlap.prepare(sender, privkey, payload).generate_xml(recipient_key.public_key) }
context "generate" do
describe ".prepare" do
context "sanity" do
it "raises an error when the sender is the wrong type" do
[1234, true, :symbol, payload, privkey].each do |val|
expect {
Salmon::EncryptedSlap.prepare(val, privkey, payload)
}.to raise_error ArgumentError
end
end
it "raises an error when the privkey is the wrong type" do
["asdf", 1234, true, :symbol, payload].each do |val|
expect {
Salmon::EncryptedSlap.prepare(sender, val, payload)
}.to raise_error ArgumentError
end
end
it "raises an error when the entity is the wrong type" do
["asdf", 1234, true, :symbol, privkey].each do |val|
expect {
Salmon::EncryptedSlap.prepare(sender, privkey, val)
}.to raise_error ArgumentError
end
end
end
end
describe ".generate_xml" do
let(:ns) { {d: Salmon::XMLNS, me: Salmon::MagicEnvelope::XMLNS} }
context "sanity" do
it "accepts correct params" do
expect {
Salmon::EncryptedSlap.prepare(sender, privkey, payload).generate_xml(recipient_key.public_key)
}.not_to raise_error
end
it "raises an error when the params are the wrong type" do
["asdf", 1234, true, :symbol, payload].each do |val|
expect {
Salmon::EncryptedSlap.prepare(sender, privkey, payload).generate_xml(val)
}.to raise_error ArgumentError
end
end
end
it "generates valid xml" do
doc = Nokogiri::XML::Document.parse(slap_xml)
expect(doc.root.name).to eq("diaspora")
expect(doc.at_xpath("d:diaspora/d:encrypted_header", ns).content).to_not be_empty
expect(doc.xpath("d:diaspora/me:env", ns)).to have(1).item
end
it "can generate xml for two people" do
slap = Salmon::EncryptedSlap.prepare(sender, privkey, payload)
doc1 = Nokogiri::XML::Document.parse(slap.generate_xml(recipient_key.public_key))
enc_header1 = doc1.at_xpath("d:diaspora/d:encrypted_header", ns).content
cipher_header1 = JSON.parse(Base64.decode64(enc_header1))
header_key1 = JSON.parse(recipient_key.private_decrypt(Base64.decode64(cipher_header1["aes_key"])))
decrypted_header1 = Salmon::AES.decrypt(cipher_header1["ciphertext"],
Base64.decode64(header_key1["key"]),
Base64.decode64(header_key1["iv"]))
recipient2_key = OpenSSL::PKey::RSA.generate(1024)
doc2 = Nokogiri::XML::Document.parse(slap.generate_xml(recipient2_key.public_key))
enc_header2 = doc2.at_xpath("d:diaspora/d:encrypted_header", ns).content
cipher_header2 = JSON.parse(Base64.decode64(enc_header2))
header_key2 = JSON.parse(recipient2_key.private_decrypt(Base64.decode64(cipher_header2["aes_key"])))
decrypted_header2 = Salmon::AES.decrypt(cipher_header2["ciphertext"],
Base64.decode64(header_key2["key"]),
Base64.decode64(header_key2["iv"]))
expect(enc_header1).not_to eq(enc_header2)
expect(header_key1).not_to eq(header_key2)
expect(decrypted_header1).to eq(decrypted_header2)
expect(doc1.xpath("d:diaspora/me:env", ns).to_xml).to eq(doc2.xpath("d:diaspora/me:env", ns).to_xml)
end
it "does not add the sender to the magic envelope" do
doc = Nokogiri::XML::Document.parse(slap_xml)
expect(doc.at_xpath("d:diaspora/me:env/me:sig", ns)["key_id"]).to be_nil
end
context "header" do
subject {
doc = Nokogiri::XML::Document.parse(slap_xml)
doc.at_xpath("d:diaspora/d:encrypted_header", ns).content
}
let(:cipher_header) { JSON.parse(Base64.decode64(subject)) }
let(:header_key) {
JSON.parse(recipient_key.private_decrypt(Base64.decode64(cipher_header["aes_key"])))
}
it "encodes the header correctly" do
json_header = {}
expect {
json_header = JSON.parse(Base64.decode64(subject))
}.not_to raise_error
expect(json_header).to include("aes_key", "ciphertext")
end
it "encrypts the public_key encrypted header correctly" do
key = {}
expect {
key = JSON.parse(recipient_key.private_decrypt(Base64.decode64(cipher_header["aes_key"])))
}.not_to raise_error
expect(key).to include("key", "iv")
end
it "encrypts the aes encrypted header correctly" do
header = ""
expect {
header = Salmon::AES.decrypt(cipher_header["ciphertext"],
Base64.decode64(header_key["key"]),
Base64.decode64(header_key["iv"]))
}.not_to raise_error
header_doc = Nokogiri::XML::Document.parse(header)
expect(header_doc.root.name).to eq("decrypted_header")
expect(header_doc.xpath("//iv")).to have(1).item
expect(header_doc.xpath("//aes_key")).to have(1).item
expect(header_doc.xpath("//author_id")).to have(1).item
expect(header_doc.at_xpath("//author_id").content).to eq(sender)
end
end
end
end
let(:slap_xml) { generate_legacy_encrypted_salmon_slap(payload, sender, privkey, recipient_key.public_key) }
describe ".from_xml" do
context "sanity" do

View file

@ -6,10 +6,10 @@ module DiasporaFederation
let(:envelope) { Salmon::MagicEnvelope.new(payload, sender) }
def sig_subj(env)
data = Base64.urlsafe_decode64(env.at_xpath("me:data").content)
type = env.at_xpath("me:data")["type"]
enc = env.at_xpath("me:encoding").content
alg = env.at_xpath("me:alg").content
data = Base64.urlsafe_decode64(env.at_xpath("me:env/me:data").content)
type = env.at_xpath("me:env/me:data")["type"]
enc = env.at_xpath("me:env/me:encoding").content
alg = env.at_xpath("me:env/me:alg").content
[data, type, enc, alg].map {|i| Base64.urlsafe_encode64(i) }.join(".")
end
@ -41,12 +41,12 @@ module DiasporaFederation
end
end
it "should be an instance of Nokogiri::XML::Element" do
expect(envelope.envelop(privkey)).to be_an_instance_of Nokogiri::XML::Element
it "should be an instance of Nokogiri::XML::Document" do
expect(envelope.envelop(privkey)).to be_an_instance_of Nokogiri::XML::Document
end
it "returns a magic envelope of correct structure" do
env_xml = envelope.envelop(privkey)
env_xml = envelope.envelop(privkey).root
expect(env_xml.name).to eq("env")
control = %w(data encoding alg sig)
@ -59,13 +59,13 @@ module DiasporaFederation
end
it "adds the sender to the signature" do
key_id = envelope.envelop(privkey).at_xpath("me:sig")["key_id"]
key_id = envelope.envelop(privkey).at_xpath("me:env/me:sig")["key_id"]
expect(Base64.urlsafe_decode64(key_id)).to eq(sender)
end
it "adds the data_type" do
data_type = envelope.envelop(privkey).at_xpath("me:data")["type"]
data_type = envelope.envelop(privkey).at_xpath("me:env/me:data")["type"]
expect(data_type).to eq("application/xml")
end
@ -74,35 +74,15 @@ module DiasporaFederation
env_xml = envelope.envelop(privkey)
subj = sig_subj(env_xml)
sig = Base64.urlsafe_decode64(env_xml.at_xpath("me:sig").content)
sig = Base64.urlsafe_decode64(env_xml.at_xpath("me:env/me:sig").content)
expect(privkey.public_key.verify(OpenSSL::Digest::SHA256.new, sig, subj)).to be_truthy
end
end
describe "#encrypt!" do
it "encrypts the payload, returning cipher params" do
params = envelope.encrypt!
expect(params).to include(:key, :iv)
end
it "actually encrypts the payload" do
plain_payload = envelope.send(:payload_data)
params = envelope.encrypt!
encrypted_payload = envelope.send(:payload_data)
cipher = OpenSSL::Cipher.new(Salmon::AES::CIPHER)
cipher.encrypt
cipher.iv = params[:iv]
cipher.key = params[:key]
ciphertext = cipher.update(plain_payload) + cipher.final
expect(Base64.strict_encode64(ciphertext)).to eq(encrypted_payload)
end
end
describe ".unenvelop" do
let(:envelope_root) { envelope.envelop(privkey).root }
context "sanity" do
before do
allow(DiasporaFederation.callbacks).to receive(:trigger).with(
@ -112,7 +92,7 @@ module DiasporaFederation
it "works with sane input" do
expect {
Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey), sender)
Salmon::MagicEnvelope.unenvelop(envelope_root, sender)
}.not_to raise_error
end
@ -126,15 +106,14 @@ module DiasporaFederation
it "verifies the envelope structure" do
expect {
Salmon::MagicEnvelope.unenvelop(Nokogiri::XML::Document.parse("<asdf/>").root, sender)
Salmon::MagicEnvelope.unenvelop(Nokogiri::XML("<asdf/>").root, sender)
}.to raise_error Salmon::InvalidEnvelope
end
it "raises if missing signature" do
bad_env = envelope.envelop(privkey)
bad_env.at_xpath("me:sig").remove
envelope_root.at_xpath("me:sig").remove
expect {
Salmon::MagicEnvelope.unenvelop(bad_env, sender)
Salmon::MagicEnvelope.unenvelop(envelope_root, sender)
}.to raise_error Salmon::InvalidEnvelope, "missing me:sig"
end
@ -145,70 +124,63 @@ module DiasporaFederation
expect_callback(:fetch_public_key, other_sender).and_return(other_key)
expect {
Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey), other_sender)
Salmon::MagicEnvelope.unenvelop(envelope_root, other_sender)
}.to raise_error Salmon::InvalidSignature
end
it "raises if missing data" do
bad_env = envelope.envelop(privkey)
bad_env.at_xpath("me:data").remove
envelope_root.at_xpath("me:data").remove
expect {
Salmon::MagicEnvelope.unenvelop(bad_env, sender)
Salmon::MagicEnvelope.unenvelop(envelope_root, sender)
}.to raise_error Salmon::InvalidEnvelope, "missing me:data"
end
it "raises if missing encoding" do
bad_env = envelope.envelop(privkey)
bad_env.at_xpath("me:encoding").remove
envelope_root.at_xpath("me:encoding").remove
expect {
Salmon::MagicEnvelope.unenvelop(bad_env, sender)
Salmon::MagicEnvelope.unenvelop(envelope_root, sender)
}.to raise_error Salmon::InvalidEncoding, "missing encoding"
end
it "verifies the encoding" do
bad_env = envelope.envelop(privkey)
bad_env.at_xpath("me:encoding").content = "invalid_enc"
envelope_root.at_xpath("me:encoding").content = "invalid_enc"
expect {
Salmon::MagicEnvelope.unenvelop(bad_env, sender)
Salmon::MagicEnvelope.unenvelop(envelope_root, sender)
}.to raise_error Salmon::InvalidEncoding, "invalid encoding: invalid_enc"
end
it "raises if missing algorithm" do
bad_env = envelope.envelop(privkey)
bad_env.at_xpath("me:alg").remove
envelope_root.at_xpath("me:alg").remove
expect {
Salmon::MagicEnvelope.unenvelop(bad_env, sender)
Salmon::MagicEnvelope.unenvelop(envelope_root, sender)
}.to raise_error Salmon::InvalidAlgorithm, "missing algorithm"
end
it "verifies the algorithm" do
bad_env = envelope.envelop(privkey)
bad_env.at_xpath("me:alg").content = "invalid_alg"
envelope_root.at_xpath("me:alg").content = "invalid_alg"
expect {
Salmon::MagicEnvelope.unenvelop(bad_env, sender)
Salmon::MagicEnvelope.unenvelop(envelope_root, sender)
}.to raise_error Salmon::InvalidAlgorithm, "invalid algorithm: invalid_alg"
end
it "raises if missing data type" do
bad_env = envelope.envelop(privkey)
bad_env.at_xpath("me:data").attributes["type"].remove
envelope_root.at_xpath("me:data").attributes["type"].remove
expect {
Salmon::MagicEnvelope.unenvelop(bad_env, sender)
Salmon::MagicEnvelope.unenvelop(envelope_root, sender)
}.to raise_error Salmon::InvalidDataType, "missing data type"
end
it "verifies the data type" do
bad_env = envelope.envelop(privkey)
bad_env.at_xpath("me:data")["type"] = "invalid_type"
envelope_root.at_xpath("me:data")["type"] = "invalid_type"
expect {
Salmon::MagicEnvelope.unenvelop(bad_env, sender)
Salmon::MagicEnvelope.unenvelop(envelope_root, sender)
}.to raise_error Salmon::InvalidDataType, "invalid data type: invalid_type"
end
end
context "generated instance" do
it_behaves_like "a MagicEnvelope instance" do
subject { Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey), sender) }
subject { Salmon::MagicEnvelope.unenvelop(envelope_root, sender) }
end
end
@ -216,8 +188,8 @@ module DiasporaFederation
expect_callback(:fetch_public_key, sender).and_return(privkey.public_key)
env = Salmon::MagicEnvelope.new(payload)
params = env.encrypt!
env_xml = env.envelop(privkey)
params = encrypt_magic_env(env)
env_xml = env.envelop(privkey).root
magic_env = Salmon::MagicEnvelope.unenvelop(env_xml, sender, params)
expect(magic_env.payload).to be_an_instance_of Entities::TestEntity
@ -227,17 +199,15 @@ module DiasporaFederation
context "use key_id from magic envelope" do
context "generated instance" do
it_behaves_like "a MagicEnvelope instance" do
subject { Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey)) }
subject { Salmon::MagicEnvelope.unenvelop(envelope_root) }
end
end
it "raises if the magic envelope has no key_id" do
bad_env = envelope.envelop(privkey)
bad_env.at_xpath("me:sig").attributes["key_id"].remove
envelope_root.at_xpath("me:sig").attributes["key_id"].remove
expect {
Salmon::MagicEnvelope.unenvelop(bad_env)
Salmon::MagicEnvelope.unenvelop(envelope_root)
}.to raise_error Salmon::InvalidEnvelope
end
@ -245,7 +215,7 @@ module DiasporaFederation
expect_callback(:fetch_public_key, sender).and_return(nil)
expect {
Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey))
Salmon::MagicEnvelope.unenvelop(envelope_root)
}.to raise_error Salmon::SenderKeyNotFound
end
end

View file

@ -3,49 +3,7 @@ module DiasporaFederation
let(:sender) { "test_user@pod.somedomain.tld" }
let(:privkey) { OpenSSL::PKey::RSA.generate(512) } # use small key for speedy specs
let(:payload) { Entities::TestEntity.new(test: "qwertzuiop") }
let(:slap_xml) { Salmon::Slap.generate_xml(sender, privkey, payload) }
describe ".generate_xml" do
context "sanity" do
it "accepts correct params" do
expect {
Salmon::Slap.generate_xml(sender, privkey, payload)
}.not_to raise_error
end
it "raises an error when the sender is the wrong type" do
[1234, true, :symbol, payload, privkey].each do |val|
expect {
Salmon::Slap.generate_xml(val, privkey, payload)
}.to raise_error ArgumentError
end
end
it "raises an error when the privkey is the wrong type" do
["asdf", 1234, true, :symbol, payload].each do |val|
expect {
Salmon::Slap.generate_xml(sender, val, payload)
}.to raise_error ArgumentError
end
end
it "raises an error when the payload is the wrong type" do
["asdf", 1234, true, :symbol, privkey].each do |val|
expect {
Salmon::Slap.generate_xml(sender, privkey, val)
}.to raise_error ArgumentError
end
end
end
it "generates valid xml" do
ns = {d: Salmon::XMLNS, me: Salmon::MagicEnvelope::XMLNS}
doc = Nokogiri::XML::Document.parse(slap_xml)
expect(doc.root.name).to eq("diaspora")
expect(doc.at_xpath("d:diaspora/d:header/d:author_id", ns).content).to eq(sender)
expect(doc.xpath("d:diaspora/me:env", ns)).to have(1).item
end
end
let(:slap_xml) { generate_legacy_salmon_slap(payload, sender, privkey) }
describe ".from_xml" do
context "sanity" do

View file

@ -1,7 +1,6 @@
module DiasporaFederation
describe Salmon::XmlPayload do
let(:entity) { Entities::TestEntity.new(test: "asdf") }
let(:payload) { Salmon::XmlPayload.pack(entity) }
let(:entity_xml) { <<-XML.strip }
<XML>
<post>
@ -12,48 +11,11 @@ module DiasporaFederation
</XML>
XML
describe ".pack" do
it "expects an Entity as param" do
expect {
Salmon::XmlPayload.pack(entity)
}.not_to raise_error
end
it "raises an error when the param is not an Entity" do
["asdf", 1234, true, :test, payload].each do |val|
expect {
Salmon::XmlPayload.pack(val)
}.to raise_error ArgumentError
end
end
context "returned xml" do
subject { Salmon::XmlPayload.pack(entity) }
it "returns an xml wrapper" do
expect(subject).to be_an_instance_of Nokogiri::XML::Element
expect(subject.name).to eq("XML")
expect(subject.children).to have(1).item
expect(subject.children[0].name).to eq("post")
expect(subject.children[0].children).to have(1).item
end
it "returns the entity xml inside the wrapper" do
expect(subject.children[0].children[0].name).to eq("test_entity")
expect(subject.children[0].children[0].children).to have(1).item
end
it "produces the expected XML" do
expect(subject.to_xml).to eq(entity_xml)
end
end
end
describe ".unpack" do
context "sanity" do
it "expects an Nokogiri::XML::Element as param" do
expect {
Salmon::XmlPayload.unpack(payload)
Salmon::XmlPayload.unpack(Nokogiri::XML(entity_xml).root)
}.not_to raise_error
end
@ -67,7 +29,7 @@ XML
end
context "returned object" do
subject { Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(entity_xml).root) }
subject { Salmon::XmlPayload.unpack(Nokogiri::XML(entity_xml).root) }
it "#to_h should match entity.to_h" do
expect(subject.to_h).to eq(entity.to_h)
@ -85,7 +47,7 @@ XML
</test_entity>
XML
entity = Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(xml).root)
entity = Salmon::XmlPayload.unpack(Nokogiri::XML(xml).root)
expect(entity).to be_an_instance_of Entities::TestEntity
expect(entity.test).to eq("asdf")

View file

@ -1,29 +0,0 @@
module DiasporaFederation
describe Validators::RelayableRetractionValidator do
let(:entity) { :relayable_retraction_entity }
it_behaves_like "a common validator"
it_behaves_like "a diaspora* ID validator" do
let(:property) { :author }
let(:mandatory) { true }
end
it_behaves_like "a guid validator" do
let(:property) { :target_guid }
end
describe "#target_type" do
it_behaves_like "a property that mustn't be empty" do
let(:property) { :target_type }
end
end
describe "#target" do
it_behaves_like "a property with a value validation/restriction" do
let(:property) { :target }
let(:wrong_values) { [nil] }
let(:correct_values) { [Fabricate(:related_entity)] }
end
end
end
end

View file

@ -1,16 +0,0 @@
module DiasporaFederation
describe Validators::RequestValidator do
let(:entity) { :request_entity }
it_behaves_like "a common validator"
%i(author recipient).each do |prop|
describe "##{prop}" do
it_behaves_like "a diaspora* ID validator" do
let(:property) { prop }
let(:mandatory) { true }
end
end
end
end
end

View file

@ -1,29 +0,0 @@
module DiasporaFederation
describe Validators::SignedRetractionValidator do
let(:entity) { :signed_retraction_entity }
it_behaves_like "a common validator"
it_behaves_like "a diaspora* ID validator" do
let(:property) { :author }
let(:mandatory) { true }
end
it_behaves_like "a guid validator" do
let(:property) { :target_guid }
end
describe "#target_type" do
it_behaves_like "a property that mustn't be empty" do
let(:property) { :target_type }
end
end
describe "#target" do
it_behaves_like "a property with a value validation/restriction" do
let(:property) { :target }
let(:wrong_values) { [nil] }
let(:correct_values) { [Fabricate(:related_entity)] }
end
end
end
end

View file

@ -0,0 +1,66 @@
# This file only exists to generate legacy XMLs to test that we can still parse it.
def generate_legacy_salmon_slap(entity, sender, sender_privkey)
build_salmon_slap_xml do |xml|
xml.header {
xml.author_id(sender)
}
xml.parent << DiasporaFederation::Salmon::MagicEnvelope.new(entity, sender).envelop(sender_privkey).root
end
end
def generate_legacy_encrypted_salmon_slap(entity, sender, sender_privkey, recipient_pubkey)
magic_envelope = DiasporaFederation::Salmon::MagicEnvelope.new(entity)
cipher_params = encrypt_magic_env(magic_envelope)
build_salmon_slap_xml do |xml|
xml.encrypted_header(encrypted_header(sender, cipher_params, recipient_pubkey))
xml.parent << magic_envelope.envelop(sender_privkey).root
end
end
def build_salmon_slap_xml
Nokogiri::XML::Builder.new(encoding: "UTF-8") {|xml|
xml.diaspora("xmlns" => DiasporaFederation::Salmon::XMLNS,
"xmlns:me" => DiasporaFederation::Salmon::MagicEnvelope::XMLNS) {
yield xml
}
}.to_xml
end
def encrypt_magic_env(magic_env)
DiasporaFederation::Salmon::AES.generate_key_and_iv.tap do |key|
magic_env.instance_variable_set(
"@payload_data", DiasporaFederation::Salmon::AES.encrypt(magic_env.send(:payload_data), key[:key], key[:iv])
)
end
end
def encrypted_header(author_id, envelope_key, pubkey)
data = decrypted_header_xml(author_id, strict_base64_encode(envelope_key))
header_key = DiasporaFederation::Salmon::AES.generate_key_and_iv
ciphertext = DiasporaFederation::Salmon::AES.encrypt(data, header_key[:key], header_key[:iv])
json_key = JSON.generate(strict_base64_encode(header_key))
encrypted_key = Base64.strict_encode64(pubkey.public_encrypt(json_key))
json_header = JSON.generate(aes_key: encrypted_key, ciphertext: ciphertext)
Base64.strict_encode64(json_header)
end
def decrypted_header_xml(author_id, envelope_key)
Nokogiri::XML::Builder.new(encoding: "UTF-8") {|xml|
xml.decrypted_header {
xml.iv(envelope_key[:iv])
xml.aes_key(envelope_key[:key])
xml.author_id(author_id)
}
}.to_xml.strip
end
def strict_base64_encode(hash)
hash.map {|k, v| [k, Base64.strict_encode64(v)] }.to_h
end

View file

@ -63,7 +63,7 @@ shared_examples "an XML Entity" do |ignored_props=[]|
context "parsing" do
it "reads its own output" do
packed_xml = DiasporaFederation::Salmon::XmlPayload.pack(instance)
packed_xml = instance.to_xml
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(packed_xml)
check_entity(instance, parsed_instance, ignored_props)
@ -106,12 +106,13 @@ shared_examples "a relayable Entity" do
end
it "computes correct signatures for the entity" do
signed_string = described_class::LEGACY_SIGNATURE_ORDER.map {|name| data[name] }.join(";")
order = described_class.class_props.keys - %i(author_signature parent_author_signature parent created_at)
signed_string = order.map {|name| data[name] }.join(";")
xml = DiasporaFederation::Salmon::XmlPayload.pack(instance)
xml = instance.to_xml
author_signature = xml.at_xpath("post/*[1]/author_signature").text
parent_author_signature = xml.at_xpath("post/*[1]/parent_author_signature").text
author_signature = xml.at_xpath("author_signature").text
parent_author_signature = xml.at_xpath("parent_author_signature").text
expect(verify_signature(alice.public_key, author_signature, signed_string)).to be_truthy
expect(verify_signature(bob.public_key, parent_author_signature, signed_string)).to be_truthy
@ -119,21 +120,6 @@ shared_examples "a relayable Entity" do
end
end
shared_examples "a retraction" do
context "receive with no target found" do
let(:unknown_guid) { Fabricate.sequence(:guid) }
let(:instance) { described_class.new(data.merge(target_guid: unknown_guid)) }
it "raises when no target is found" do
xml = instance.to_xml
expect {
described_class.from_xml(xml)
}.to raise_error DiasporaFederation::Entities::Retraction::TargetNotFound,
"not found: #{data[:target_type]}:#{unknown_guid}"
end
end
end
shared_examples "a JSON Entity" do
describe "#to_json" do
it "#to_json output matches JSON schema" do