Merge pull request #62 from SuperTux88/protocol-next-step
Next step for the new protocol
This commit is contained in:
commit
51f73b86fc
65 changed files with 541 additions and 1388 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ module DiasporaFederation
|
|||
|
||||
let(:xml) { <<-XML }
|
||||
<account_deletion>
|
||||
<diaspora_handle>#{data[:author]}</diaspora_handle>
|
||||
<author>#{data[:author]}</author>
|
||||
</account_deletion>
|
||||
XML
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
66
spec/support/legacy_helper.rb
Normal file
66
spec/support/legacy_helper.rb
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue