Merge pull request #114 from SuperTux88/remove-old-federation

Remove old federation
This commit is contained in:
Benjamin Neff 2021-06-30 00:57:24 +02:00 committed by GitHub
commit 7fd94438a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 146 additions and 1345 deletions

View file

@ -5,18 +5,14 @@ require_dependency "diaspora_federation/application_controller"
module DiasporaFederation
# This controller processes receiving messages.
class ReceiveController < ApplicationController
before_action :check_for_xml
# Receives public messages
#
# POST /receive/public
def public
legacy = request.content_type != "application/magic-envelope+xml"
data = data_for_public_message(legacy)
data = request.body.read
logger.debug data
DiasporaFederation.callbacks.trigger(:queue_public_receive, data, legacy)
DiasporaFederation.callbacks.trigger(:queue_public_receive, data)
head :accepted
end
@ -25,43 +21,12 @@ module DiasporaFederation
#
# POST /receive/users/:guid
def private
legacy = request.content_type != "application/json"
data = data_for_private_message(legacy)
data = request.body.read
logger.debug data
success = DiasporaFederation.callbacks.trigger(:queue_private_receive, params[:guid], data, legacy)
success = DiasporaFederation.callbacks.trigger(:queue_private_receive, params[:guid], data)
head success ? :accepted : :not_found
end
private
# Checks the xml parameter for legacy salmon slaps
# @deprecated
def check_for_xml
legacy_request = request.content_type.nil? || request.content_type == "application/x-www-form-urlencoded"
head :unprocessable_entity if params[:xml].nil? && legacy_request
end
def data_for_public_message(legacy)
if legacy
logger.info "received a public salmon slap"
CGI.unescape(params[:xml])
else
logger.info "received a public magic envelope"
request.body.read
end
end
def data_for_private_message(legacy)
if legacy
logger.info "received a private salmon slap for #{params[:guid]}"
CGI.unescape(params[:xml])
else
logger.info "received a private encrypted magic envelope for #{params[:guid]}"
request.body.read
end
end
end
end

View file

@ -33,7 +33,6 @@ See also: [Relayable][relayable]
<parent_guid>bb8371f0b1c901342ebd55853a9b5d75</parent_guid>
<status>accepted</status>
<author_signature>dT6KbT7kp0bE+s3//ZErxO1wvVIqtD0lY67i81+dO43B4D2m5kjCdzW240eWt/jZmcHIsdxXf4WHNdrb6ZDnamA8I1FUVnLjHA9xexBITQsSLXrcV88UdammSmmOxl1Ac4VUXqFpdavm6a7/MwOJ7+JHP8TbUO9siN+hMfgUbtY=</author_signature>
<parent_author_signature/>
</event_participation>
~~~
@ -46,7 +45,6 @@ See also: [Relayable][relayable]
<parent_guid>bb8371f0b1c901342ebd55853a9b5d75</parent_guid>
<status>accepted</status>
<author_signature>dT6KbT7kp0bE+s3//ZErxO1wvVIqtD0lY67i81+dO43B4D2m5kjCdzW240eWt/jZmcHIsdxXf4WHNdrb6ZDnamA8I1FUVnLjHA9xexBITQsSLXrcV88UdammSmmOxl1Ac4VUXqFpdavm6a7/MwOJ7+JHP8TbUO9siN+hMfgUbtY=</author_signature>
<parent_author_signature>gWasNPpSnMcKBIMWyzfoVO6sr8eRYkhUqy3PIkkh53n/ki+DM9mnh3ayotI0+6un9aq1N3XkS7Vn05ZD3+nHVby6i21XkYgPnbD8pWYuBBj7VGPyahT70BUs/vSvY8KX8V3wYfsPsaiAgJsAFg2UHYdY3r4/oWdIIbBZc21O3zk=</parent_author_signature>
</event_participation>
~~~

View file

@ -184,13 +184,11 @@ module DiasporaFederation
# queue_public_receive
# Queue a public salmon xml to process in background
# @param [String] data salmon slap xml or magic envelope xml
# @param [Boolean] legacy true if it is a legacy salmon slap, false if it is a magic envelope xml
#
# queue_private_receive
# Queue a private salmon xml to process in background
# @param [String] guid guid of the receiver person
# @param [String] data salmon slap xml or encrypted magic envelope json
# @param [Boolean] legacy true if it is a legacy salmon slap, false if it is a encrypted magic envelope json
# @return [Boolean] true if successful, false if the user was not found
#
# receive_entity

View file

@ -46,8 +46,3 @@ require "diaspora_federation/entities/message"
require "diaspora_federation/entities/conversation"
require "diaspora_federation/entities/retraction"
# deprecated
require "diaspora_federation/entities/request"
require "diaspora_federation/entities/signed_retraction"
require "diaspora_federation/entities/relayable_retraction"

View file

@ -14,7 +14,7 @@ module DiasporaFederation
# Alias for author
# @see AccountDeletion#author
# @return [String] diaspora* ID
property :author, :string, alias: :diaspora_id, xml_name: :diaspora_handle
property :author, :string, alias: :diaspora_id
# @return [String] string representation of this object
def to_s

View file

@ -10,7 +10,7 @@ module DiasporaFederation
# The diaspora* ID of the person initiated the conversation
# @see Person#author
# @return [String] diaspora* ID
property :author, :string, xml_name: :diaspora_handle
property :author, :string
# @!attribute [r] guid
# A random string of at least 16 chars
@ -29,7 +29,7 @@ module DiasporaFederation
# @!attribute [r] participants
# The diaspora* IDs of the persons participating the conversation separated by ";"
# @return [String] participants diaspora* IDs
property :participants, :string, xml_name: :participant_handles
property :participants, :string
# @!attribute [r] messages
# @return [[Entities::Message]] Messages of this conversation

View file

@ -13,7 +13,7 @@ module DiasporaFederation
# 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
property :parent_type, :string
# @!attribute [r] positive
# If +true+ set a like, if +false+, set a dislike (dislikes are currently not

View file

@ -10,7 +10,7 @@ module DiasporaFederation
# The diaspora* ID of the author
# @see Person#author
# @return [String] diaspora* ID
property :author, :string, xml_name: :diaspora_handle
property :author, :string
# @!attribute [r] guid
# A random string of at least 16 chars

View file

@ -10,7 +10,7 @@ module DiasporaFederation
# The diaspora* ID of the author
# @see Person#author
# @return [String] diaspora* ID
property :author, :string, xml_name: :diaspora_handle
property :author, :string
# @!attribute [r] guid
# A random string of at least 16 chars
@ -27,7 +27,7 @@ module DiasporaFederation
# A string describing a type of the target to subscribe on
# Currently only "Post" is supported.
# @return [String] parent type
property :parent_type, :string, xml_name: :target_type
property :parent_type, :string
# @return [String] string representation of this object
def to_s

View file

@ -21,7 +21,7 @@ module DiasporaFederation
# alias for author
# @see Person#author
# @return [String] diaspora* ID
property :author, :string, alias: :diaspora_id, xml_name: :diaspora_handle
property :author, :string, alias: :diaspora_id
# @!attribute [r] url
# @see Discovery::WebFinger#seed_url

View file

@ -16,7 +16,7 @@ module DiasporaFederation
# The diaspora* ID of the person who uploaded the photo
# @see Person#author
# @return [String] author diaspora* ID
property :author, :string, xml_name: :diaspora_handle
property :author, :string
# @!attribute [r] public
# Points if the photo is visible to everyone or only to some aspects

View file

@ -32,7 +32,7 @@ module DiasporaFederation
# @param [Entity] entity the entity in which it is included
def self.included(entity)
entity.class_eval do
property :author, :string, xml_name: :diaspora_handle
property :author, :string
property :guid, :string
property :created_at, :timestamp, default: -> { Time.now.utc }
property :public, :boolean, default: false

View file

@ -14,7 +14,7 @@ module DiasporaFederation
# Alias for author
# @see Profile#author
# @return [String] diaspora* ID
property :author, :string, alias: :diaspora_id, xml_name: :diaspora_handle
property :author, :string, alias: :diaspora_id
# @!attribute [r] edited_at
# The timestamp when the profile was edited

View file

@ -35,12 +35,6 @@ module DiasporaFederation
# a target pod.
# @return [String] author signature
#
# @!attribute [r] parent_author_signature
# Contains a signature of the entity using the private key of the author of a parent post.
# @deprecated This signature isn't required anymore, because we can check the signature from
# the parent author in the MagicEnvelope.
# @return [String] parent author signature
#
# @!attribute [r] parent
# Meta information about the parent object
# @return [RelatedEntity] parent entity
@ -48,11 +42,10 @@ module DiasporaFederation
# @param [Entity] klass the entity in which it is included
def self.included(klass)
klass.class_eval do
property :author, :string, xml_name: :diaspora_handle
property :author, :string
property :guid, :string
property :parent_guid, :string
property :author_signature, :string, default: nil
property :parent_author_signature, :string, default: nil
entity :parent, Entities::RelatedEntity
end
@ -67,7 +60,7 @@ module DiasporaFederation
# @see DiasporaFederation::Entity#initialize
def initialize(data, signature_order=nil, additional_data={})
self.signature_order = signature_order if signature_order
@additional_data = additional_data
self.additional_data = additional_data
super(data)
end
@ -104,7 +97,7 @@ module DiasporaFederation
def signature_order
@signature_order || self.class.class_props.keys.reject {|key|
self.class.optional_props.include?(key) && public_send(key).nil?
} - %i[author_signature parent_author_signature parent]
} - %i[author_signature parent]
end
private
@ -121,17 +114,6 @@ module DiasporaFederation
end
end
# Sign with parent author key, if the parent author is local (if the private key is found)
# @return [String] A Base64 encoded signature of #signature_data with key
def sign_with_parent_author_if_available
privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key, parent.root.author)
return unless privkey
sign_with_key(privkey).tap do
logger.info "event=sign status=complete signature=parent_author_signature obj=#{self}"
end
end
# Update the signatures with the keys of the author and the parent
# if the signatures are not there yet and if the keys are available.
#
@ -139,7 +121,6 @@ module DiasporaFederation
def enriched_properties
super.merge(additional_data).tap do |hash|
hash[:author_signature] = author_signature || sign_with_author
hash.delete(:parent_author_signature)
end
end
@ -147,10 +128,8 @@ module DiasporaFederation
#
# @return [Hash] sorted xml elements
def xml_elements
data = super.tap do |hash|
hash[:parent_author_signature] = parent_author_signature || sign_with_parent_author_if_available.to_s
end
order = signature_order + %i[author_signature parent_author_signature]
data = super
order = signature_order + %i[author_signature]
order.map {|element| [element, data[element].to_s] }.to_h
end
@ -160,6 +139,10 @@ module DiasporaFederation
.map {|name| prop_names.include?(name) ? name.to_sym : name }
end
def additional_data=(additional_data)
@additional_data = additional_data.reject {|name, _| name =~ /signature/ }
end
# @return [String] signature data string
def signature_data
data = normalized_properties.merge(additional_data)

View file

@ -1,68 +0,0 @@
# frozen_string_literal: true
module DiasporaFederation
module Entities
# This entity represents a claim of deletion of a previously federated
# relayable entity. ({Entities::Comment}, {Entities::Like})
#
# There are two cases of federation of the RelayableRetraction.
# Retraction from the dowstream object owner is when an author of the
# relayable (e.g. Comment) deletes it themself. In this case only target_author_signature
# is filled and a retraction is sent to the commented post's author. Here
# the upstream object owner signs it with the parent's author key, puts
# the signature in parent_author_signature and sends it to other pods where
# other participating people are present. This is the second case - retraction
# from the upstream object owner.
# Retraction from the upstream object owner can also be performed by the
# upstream object owner themself - they have a right to delete comments on their posts.
# In any case in the retraction by the upstream author target_author_signature
# is not checked, only parent_author_signature is checked.
#
# @see Validators::RelayableRetractionValidator
# @deprecated will be replaced with {Entities::Retraction}
class RelayableRetraction < Entity
# @!attribute [r] parent_author_signature
# Contains a signature of the entity using the private key of the author of a parent post.
# This signature is mandatory only when federating from an upstream author to the subscribers.
# @see Relayable#parent_author_signature
# @return [String] parent author signature
property :parent_author_signature, :string, default: nil
# @!attribute [r] target_guid
# Guid of a relayable to be deleted
# @see Comment#guid
# @return [String] target guid
property :target_guid, :string
# @!attribute [r] target_type
# A string describing a type of the target
# @see Retraction#target_type
# @return [String] target type
property :target_type, :string
# @!attribute [r] author
# The diaspora* ID of the person who deletes a relayable
# @see Person#author
# @return [String] diaspora* ID
property :author, :string, xml_name: :sender_handle
# @!attribute [r] target_author_signature
# Contains a signature of the entity using the private key of the
# author of a federated relayable entity. ({Entities::Comment}, {Entities::Like})
# This signature is mandatory only when federation from the subscriber to an upstream
# author is done.
# @see Relayable#author_signature
# @return [String] target author signature
property :target_author_signature, :string, default: nil
def initialize(*)
raise "Sending RelayableRetraction is not supported anymore! Use Retraction instead!"
end
# @return [Retraction] instance
def self.from_hash(hash)
Retraction.from_hash(hash)
end
end
end
end

View file

@ -1,33 +0,0 @@
# frozen_string_literal: true
module DiasporaFederation
module Entities
# This entity represents a sharing request for a user. A user issues it
# when they start sharing with another user.
#
# @see Validators::RequestValidator
# @deprecated will be replaced with {Contact}
class Request < Entity
# @!attribute [r] author
# The diaspora* ID of the person who share their profile
# @see Person#author
# @return [String] sender ID
property :author, :string, xml_name: :sender_handle
# @!attribute [r] recipient
# The diaspora* ID of the person who will be shared with
# @see Validation::Rule::DiasporaId
# @return [String] recipient ID
property :recipient, :string, xml_name: :recipient_handle
def initialize(*)
raise "Sending Request is not supported anymore! Use Contact instead!"
end
# @return [Retraction] instance
def self.from_hash(hash)
Contact.new(hash)
end
end
end
end

View file

@ -10,7 +10,7 @@ module DiasporaFederation
# The diaspora* ID of the person who reshares the post
# @see Person#author
# @return [String] diaspora* ID
property :author, :string, xml_name: :diaspora_handle
property :author, :string
# @!attribute [r] guid
# A random string of at least 16 chars
@ -27,7 +27,7 @@ module DiasporaFederation
# The diaspora* ID of the person who posted the original post
# @see Person#author
# @return [String] diaspora* ID
property :root_author, :string, optional: true, xml_name: :root_diaspora_id
property :root_author, :string, optional: true
# @!attribute [r] root_guid
# Guid of the original post

View file

@ -10,17 +10,17 @@ module DiasporaFederation
# The diaspora* ID of the person who deletes the entity
# @see Person#author
# @return [String] diaspora* ID
property :author, :string, xml_name: :diaspora_handle
property :author, :string
# @!attribute [r] target_guid
# Guid of the entity to be deleted
# @return [String] target guid
property :target_guid, :string, xml_name: :post_guid
property :target_guid, :string
# @!attribute [r] target_type
# A string describing the type of the target
# @return [String] target type
property :target_type, :string, xml_name: :type
property :target_type, :string
# @!attribute [r] target
# Target entity

View file

@ -1,45 +0,0 @@
# frozen_string_literal: true
module DiasporaFederation
module Entities
# This entity represents a claim of deletion of a previously federated
# entity of post type. ({Entities::StatusMessage})
#
# @see Validators::SignedRetractionValidator
# @deprecated will be replaced with {Entities::Retraction}
class SignedRetraction < Entity
# @!attribute [r] target_guid
# Guid of a post to be deleted
# @see Retraction#target_guid
# @return [String] target guid
property :target_guid, :string
# @!attribute [r] target_type
# A string describing the type of the target
# @see Retraction#target_type
# @return [String] target type
property :target_type, :string
# @!attribute [r] author
# The diaspora* ID of the person who deletes a post
# @see Person#author
# @return [String] diaspora* ID
property :author, :string, xml_name: :sender_handle
# @!attribute [r] author_signature
# Contains a signature of the entity using the private key of the author of a post
# This signature is mandatory.
# @return [String] author signature
property :target_author_signature, :string, default: nil
def initialize(*)
raise "Sending SignedRetraction is not supported anymore! Use Retraction instead!"
end
# @return [Retraction] instance
def self.from_hash(hash)
Retraction.from_hash(hash)
end
end
end
end

View file

@ -11,7 +11,7 @@ module DiasporaFederation
# @!attribute [r] text
# Text of the status message composed by the user
# @return [String] text of the status message
property :text, :string, xml_name: :raw_message
property :text, :string
# @!attribute [r] edited_at
# The timestamp when the status message was edited

View file

@ -17,7 +17,6 @@ module DiasporaFederation
# property :prop
# property :optional, default: false
# property :dynamic_default, default: -> { Time.now }
# property :another_prop, xml_name: :another_name
# entity :nested, NestedEntity
# entity :multiple, [OtherEntity]
# end

View file

@ -8,14 +8,9 @@ module DiasporaFederation
# Receive a public message
# @param [String] data message to receive
# @param [Boolean] legacy use old slap parser
def self.receive_public(data, legacy=false)
magic_env = if legacy
Salmon::Slap.from_xml(data)
else
magic_env_xml = Nokogiri::XML(data).root
Salmon::MagicEnvelope.unenvelop(magic_env_xml)
end
def self.receive_public(data)
magic_env_xml = Nokogiri::XML(data).root
magic_env = Salmon::MagicEnvelope.unenvelop(magic_env_xml)
Public.new(magic_env).receive
rescue => e # rubocop:disable Style/RescueStandardError
logger.error "failed to receive public message: #{e.class}: #{e.message}"
@ -28,16 +23,11 @@ module DiasporaFederation
# @param [OpenSSL::PKey::RSA] recipient_private_key recipient private key to decrypt the message
# @param [Object] recipient_id the identifier to persist the entity for the correct user,
# see +receive_entity+ callback
# @param [Boolean] legacy use old slap parser
def self.receive_private(data, recipient_private_key, recipient_id, legacy=false)
def self.receive_private(data, recipient_private_key, recipient_id)
raise ArgumentError, "no recipient key provided" unless recipient_private_key.instance_of?(OpenSSL::PKey::RSA)
magic_env = if legacy
Salmon::EncryptedSlap.from_xml(data, recipient_private_key)
else
magic_env_xml = Salmon::EncryptedMagicEnvelope.decrypt(data, recipient_private_key)
Salmon::MagicEnvelope.unenvelop(magic_env_xml)
end
magic_env_xml = Salmon::EncryptedMagicEnvelope.decrypt(data, recipient_private_key)
magic_env = Salmon::MagicEnvelope.unenvelop(magic_env_xml)
Private.new(magic_env, recipient_id).receive
rescue => e # rubocop:disable Style/RescueStandardError
logger.error "failed to receive private message for #{recipient_id}: #{e.class}: #{e.message}"

View file

@ -18,7 +18,7 @@ module DiasporaFederation
def parse_entity_data(entity_data)
hash = entity_data.map {|key, value|
property = entity_type.find_property_for_xml_name(key)
property = entity_type.class_props.keys.find {|name| name.to_s == key }
if property
type = entity_type.class_props[property]
[property, parse_element_from_value(type, entity_data[key])]

View file

@ -15,14 +15,12 @@ module DiasporaFederation
from_xml_sanity_validation(root_node)
hash = root_node.element_children.map {|child|
xml_name = child.name
property = entity_type.find_property_for_xml_name(xml_name)
property, type = find_property_for(child.name)
if property
type = class_properties[property]
value = parse_element_from_node(xml_name, type, root_node)
value = parse_element_from_node(child.name, type, root_node)
[property, value]
else
[xml_name, child.text]
[child.name, child.text]
end
}.to_h
@ -31,6 +29,18 @@ module DiasporaFederation
private
def find_property_for(xml_name)
class_properties.find {|name, type|
if type.instance_of?(Symbol)
name.to_s == xml_name
elsif type.instance_of?(Array)
type.first.entity_name == xml_name
elsif type.ancestors.include?(Entity)
type.entity_name == xml_name
end
}
end
# @param [String] name property name to parse
# @param [Class, Symbol] type target type to parse
# @param [Nokogiri::XML::Element] root_node XML node to parse
@ -53,7 +63,6 @@ module DiasporaFederation
# @return [String] data
def parse_string_from_node(name, type, root_node)
node = root_node.xpath(name.to_s)
node = root_node.xpath(xml_names[name].to_s) if node.empty?
parse_string(type, node.first.text) if node.any?
end

View file

@ -8,7 +8,6 @@ module DiasporaFederation
# property :prop
# property :optional, default: false
# property :dynamic_default, default: -> { Time.now }
# property :another_prop, xml_name: :another_name
# property :original_prop, alias: :alias_prop
# entity :nested, NestedEntity
# entity :multiple, [OtherEntity]
@ -24,7 +23,6 @@ module DiasporaFederation
# @param [Hash] opts further options
# @option opts [Object, #call] :default a default value, making the
# property optional
# @option opts [Symbol] :xml_name another name used for xml generation
def property(name, type, opts={})
raise InvalidType unless property_type_valid?(type)
@ -79,49 +77,14 @@ module DiasporaFederation
}.to_h
end
# @return [Symbol] alias for the xml-generation/parsing
# @deprecated
def xml_names
@xml_names ||= {}
end
# Finds a property by +xml_name+ or +name+
# @param [String] xml_name name of the property from the received xml
# @return [Hash] the property data
def find_property_for_xml_name(xml_name)
class_props.keys.find {|name| [name.to_s, xml_names[name].to_s].include?(xml_name) }
end
private
# @deprecated
def determine_xml_name(name, type, opts={})
if !type.instance_of?(Symbol) && opts.has_key?(:xml_name)
raise ArgumentError, "xml_name is not supported for nested entities"
end
if type.instance_of?(Symbol)
if opts.has_key? :xml_name
raise InvalidName, "invalid xml_name" unless name_valid?(opts[:xml_name])
opts[:xml_name]
else
name
end
elsif type.instance_of?(Array)
type.first.entity_name.to_sym
elsif type.ancestors.include?(Entity)
type.entity_name.to_sym
end
end
def define_property(name, type, opts={})
raise InvalidName unless name_valid?(name)
class_props[name] = type
optional_props << name if opts[:optional]
default_props[name] = opts[:default] if opts.has_key? :default
xml_names[name] = determine_xml_name(name, type, opts)
instance_eval { attr_reader name }

View file

@ -13,8 +13,5 @@ require "base64"
require "diaspora_federation/salmon/aes"
require "diaspora_federation/salmon/exceptions"
require "diaspora_federation/salmon/xml_payload"
require "diaspora_federation/salmon/magic_envelope"
require "diaspora_federation/salmon/encrypted_magic_envelope"
require "diaspora_federation/salmon/slap"
require "diaspora_federation/salmon/encrypted_slap"

View file

@ -1,113 +0,0 @@
# frozen_string_literal: true
require "json"
module DiasporaFederation
module Salmon
# +EncryptedSlap+ provides class methods for generating and parsing encrypted
# Slaps. (In principle the same as {Slap}, but with encryption.)
#
# The basic encryption mechanism used here is based on the knowledge that
# asymmetrical encryption is slow and symmetrical encryption is fast. Keeping in
# mind that a message we want to de-/encrypt may greatly vary in length,
# performance considerations must play a part of this scheme.
#
# A diaspora*-flavored encrypted magic-enveloped XML message looks like the following:
#
# <?xml version="1.0" encoding="UTF-8"?>
# <diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env">
# <encrypted_header>{encrypted_header}</encrypted_header>
# {magic_envelope with encrypted data}
# </diaspora>
#
# The encrypted header is encoded in JSON like this (when in plain text):
#
# {
# "aes_key" => "...",
# "ciphertext" => "..."
# }
#
# +aes_key+ is encrypted using the recipients public key, and contains the AES
# +key+ and +iv+ used to encrypt the +ciphertext+ also encoded as JSON.
#
# {
# "key" => "...",
# "iv" => "..."
# }
#
# +ciphertext+, once decrypted, contains the +author_id+, +aes_key+ and +iv+
# relevant to the decryption of the data in the magic_envelope and the
# verification of its signature.
#
# The decrypted cyphertext has this XML structure:
#
# <decrypted_header>
# <iv>{iv}</iv>
# <aes_key>{aes_key}</aes_key>
# <author_id>{author_id}</author_id>
# </decrypted_header>
#
# Finally, before decrypting the magic envelope payload, the signature should
# first be verified.
#
# @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
# Creates a {MagicEnvelope} instance from the data within the given XML string
# containing an encrypted payload.
#
# @param [String] slap_xml encrypted Salmon xml
# @param [OpenSSL::PKey::RSA] privkey recipient private_key for decryption
#
# @return [MagicEnvelope] magic envelope instance with payload and sender
#
# @raise [ArgumentError] if any of the arguments is of the wrong type
# @raise [MissingHeader] if the +encrypted_header+ element is missing in the XML
# @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(slap_xml)
header_elem = doc.at_xpath("d:diaspora/d:encrypted_header", Slap::NS)
raise MissingHeader if header_elem.nil?
header = header_data(header_elem.content, privkey)
sender = header[:author_id]
cipher_params = {key: Base64.decode64(header[:aes_key]), iv: Base64.decode64(header[:iv])}
MagicEnvelope.unenvelop(magic_env_from_doc(doc), sender, cipher_params)
end
# 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
# @return [Hash] { iv: "...", aes_key: "...", author_id: "..." }
private_class_method def self.header_data(data, privkey)
header_elem = decrypt_header(data, privkey)
raise InvalidHeader unless header_elem.name == "decrypted_header"
iv = header_elem.at_xpath("iv").content
key = header_elem.at_xpath("aes_key").content
author_id = header_elem.at_xpath("author_id").content
{iv: iv, aes_key: key, author_id: author_id}
end
# Decrypts the xml header
# @param [String] data base64 encoded, encrypted header data
# @param [OpenSSL::PKey::RSA] privkey private key for decryption
# @return [Nokogiri::XML::Element] header xml document
private_class_method def self.decrypt_header(data, privkey)
cipher_header = JSON.parse(Base64.decode64(data))
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(xml).root
end
end
end
end

View file

@ -2,26 +2,6 @@
module DiasporaFederation
module Salmon
# Raised, if the element containing the Magic Envelope is missing from the XML
# @deprecated
class MissingMagicEnvelope < RuntimeError
end
# Raised, if the element containing the author is empty.
# @deprecated
class MissingAuthor < RuntimeError
end
# Raised, if the element containing the header is missing from the XML
# @deprecated
class MissingHeader < RuntimeError
end
# Raised, if the decrypted header has an unexpected XML structure
# @deprecated
class InvalidHeader < RuntimeError
end
# Raised, if failed to fetch the public key of the sender of the received message
class SenderKeyNotFound < RuntimeError
end

View file

@ -114,7 +114,8 @@ module DiasporaFederation
logger.debug "unenvelop message from #{sender}:\n#{data}"
new(XmlPayload.unpack(Nokogiri::XML(data).root), sender)
xml = Nokogiri::XML(data).root
new(Entity.entity_class(xml.name).from_xml(xml), sender)
end
private

View file

@ -1,59 +0,0 @@
# frozen_string_literal: true
module DiasporaFederation
module Salmon
# +Slap+ provides class methods to create unencrypted Slap XML from payload
# data and parse incoming XML into a Slap instance.
#
# A diaspora* flavored magic-enveloped XML message looks like the following:
#
# <?xml version="1.0" encoding="UTF-8"?>
# <diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env">
# <header>
# <author_id>{author}</author_id>
# </header>
# {magic_envelope}
# </diaspora>
#
# @example Parsing a Salmon Slap
# entity = Slap.from_xml(slap_xml).payload
#
# @deprecated
class Slap
# Namespaces
NS = {d: Salmon::XMLNS, me: MagicEnvelope::XMLNS}.freeze
# Parses an unencrypted Salmon XML string and returns a new instance of
# {MagicEnvelope} with the XML data.
#
# @param [String] slap_xml Salmon XML
#
# @return [MagicEnvelope] magic envelope instance with payload and sender
#
# @raise [ArgumentError] if the argument is not a String
# @raise [MissingAuthor] if the +author_id+ element is missing from the XML
# @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(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?
sender = author_elem.content
MagicEnvelope.unenvelop(magic_env_from_doc(doc), sender)
end
# Parses the magic envelop from the document.
#
# @param [Nokogiri::XML::Document] doc Salmon XML Document
private_class_method def self.magic_env_from_doc(doc)
doc.at_xpath("d:diaspora/me:env", Slap::NS).tap do |env|
raise MissingMagicEnvelope if env.nil?
end
end
end
end
end

View file

@ -1,43 +0,0 @@
# frozen_string_literal: true
module DiasporaFederation
module Salmon
# +XmlPayload+ provides methods to wrap a XML-serialized {Entity} inside a
# common XML structure that will become the payload for federation messages.
#
# The wrapper looks like so:
# <XML>
# <post>
# {data}
# </post>
# </XML>
#
# (The +post+ element is there for historic reasons...)
# @deprecated
module XmlPayload
# 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.
#
# @param [Nokogiri::XML::Element] xml payload XML root node
# @return [Entity] re-constructed Entity instance
# @raise [ArgumentError] if the argument is not an
# {http://www.rubydoc.info/gems/nokogiri/Nokogiri/XML/Element Nokogiri::XML::Element}
# @raise [UnknownEntity] if the class for the entity contained inside the
# XML can't be found
def self.unpack(xml)
raise ArgumentError, "only Nokogiri::XML::Element allowed" unless xml.instance_of?(Nokogiri::XML::Element)
data = xml_wrapped?(xml) ? xml.at_xpath("post/*[1]") : xml
Entity.entity_class(data.name).from_xml(data)
end
# @param [Nokogiri::XML::Element] element
private_class_method def self.xml_wrapped?(element)
(element.name == "XML" && !element.at_xpath("post").nil? &&
!element.at_xpath("post").children.empty?)
end
end
end
end

View file

@ -5,32 +5,6 @@ module DiasporaFederation
routes { DiasporaFederation::Engine.routes }
describe "POST #public" do
context "legacy salmon slap" do
it "returns a 422 if no xml is passed" do
post :public
expect(response.code).to eq("422")
end
it "returns a 422 if no xml is passed with content-type application/x-www-form-urlencoded" do
@request.env["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
post :public
expect(response.code).to eq("422")
end
it "returns a 202 if queued correctly" do
expect_callback(:queue_public_receive, "<diaspora/>", true)
post :public, params: {xml: "<diaspora/>"}
expect(response.code).to eq("202")
end
it "unescapes the xml before sending it to the callback" do
expect_callback(:queue_public_receive, "<diaspora/>", true)
post :public, params: {xml: CGI.escape("<diaspora/>")}
end
end
context "magic envelope" do
before do
Mime::Type.register("application/magic-envelope+xml", :magic_envelope)
@ -38,7 +12,7 @@ module DiasporaFederation
end
it "returns a 202 if queued correctly" do
expect_callback(:queue_public_receive, "<me:env/>", false)
expect_callback(:queue_public_receive, "<me:env/>")
post :public, body: +"<me:env/>"
expect(response.code).to eq("202")
@ -47,39 +21,6 @@ module DiasporaFederation
end
describe "POST #private" do
context "legacy salmon slap" do
it "return a 404 if not queued successfully (unknown user guid)" do
expect_callback(:queue_private_receive, "any-guid", "<diaspora/>", true).and_return(false)
post :private, params: {guid: "any-guid", xml: "<diaspora/>"}
expect(response.code).to eq("404")
end
it "returns a 422 if no xml is passed" do
post :private, params: {guid: "any-guid"}
expect(response.code).to eq("422")
end
it "returns a 422 if no xml is passed with content-type application/x-www-form-urlencoded" do
@request.env["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
post :private, params: {guid: "any-guid"}
expect(response.code).to eq("422")
end
it "returns a 202 if the callback returned true" do
expect_callback(:queue_private_receive, "any-guid", "<diaspora/>", true).and_return(true)
post :private, params: {guid: "any-guid", xml: "<diaspora/>"}
expect(response.code).to eq("202")
end
it "unescapes the xml before sending it to the callback" do
expect_callback(:queue_private_receive, "any-guid", "<diaspora/>", true).and_return(true)
post :private, params: {guid: "any-guid", xml: CGI.escape("<diaspora/>")}
end
end
context "encrypted magic envelope" do
before do
@request.env["CONTENT_TYPE"] = "application/json"
@ -87,7 +28,7 @@ module DiasporaFederation
it "return a 404 if not queued successfully (unknown user guid)" do
expect_callback(
:queue_private_receive, "any-guid", "{\"aes_key\": \"key\", \"encrypted_magic_envelope\": \"env\"}", false
:queue_private_receive, "any-guid", "{\"aes_key\": \"key\", \"encrypted_magic_envelope\": \"env\"}"
).and_return(false)
post :private,
@ -98,7 +39,7 @@ module DiasporaFederation
it "returns a 202 if the callback returned true" do
expect_callback(
:queue_private_receive, "any-guid", "{\"aes_key\": \"key\", \"encrypted_magic_envelope\": \"env\"}", false
:queue_private_receive, "any-guid", "{\"aes_key\": \"key\", \"encrypted_magic_envelope\": \"env\"}"
).and_return(true)
post :private,

View file

@ -28,11 +28,6 @@ module DiasporaFederation
entity :multi, [OtherEntity]
end
class TestEntityWithXmlName < DiasporaFederation::Entity
property :test, :string
property :qwer, :string, xml_name: :asdf
end
class TestEntityWithRelatedEntity < DiasporaFederation::Entity
property :test, :string
entity :parent, RelatedEntity

View file

@ -27,31 +27,6 @@ module DiasporaFederation
# g6zpg1zxGahpmxwqFQIDAQAB
# -----END PUBLIC KEY-----
let(:parent_serialized_key) { <<~KEY }
-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQDrOvW1UArKoUOg54XWXcTD3jU0zKG3Pm9IeaEzfQtApogQ3+M/
F9nz0i3q8UhTDEPBQ3hMbqJ/4qfY+wFulxMR58DbqxFx9QcNZISUd0CPx/fJOYMx
R7bygTbiCet4FAiyMjxOX3Oei/DedUNps1RAP1bu+80iibze/Kk9BgMm0QIDAQAB
AoGAMHvikRCCaOl8SvnteBWzrLtsNAnJez9/KG0JcNdhLl4kxXWgHS0JW1wC4t4A
jj2E6ZzCet6C1+Ebv3lc/jJdV3pCK3wgX0YAt/oBW5kcuvpLHLSWusWHnHkYU+qO
4SdC3bRhdLV9o3u/oCWzmdeKTdqIyNd2yAbb3W1TsD4EsQECQQD6w+vWVKhWbVOj
Ky3ZkLCxPcWNUt+7OIzDA1OLKhdhe44hIoRMfDT6iLK3sJTSjgOv0OFTfsdOqh5y
ZqYp/CTpAkEA8CQFKkAYt4qG1lKMPsU/Tme0/Z24VozDRnyw7r663f0De+25kXGY
PSBiOHYcAE6poYQEtR/leLTSaG3YZm7hqQJBAOLAWLg1Uwbb0v4/pDUQlgWfQsy4
/KAx0W7hyiCTzhKTBAFIUfNLeSh2hYx+ewQt8H2B1s6GXDjwsZlm4qgiXUkCQQC9
B12ZeIL8V2r0Yl5LOvEuQqxRx0lHt94vKhAMns5x16xabTLZrlVsKIWodDBufX1B
yq359XWooo3N7kmduEKhAkEAppzKLuVtX1XPL4VZBex/M2ewngjkSg964BvxIBwv
bFzeSqlMpnbEoOJ9hhx6CsP6Y7V19DRRXi0XgwcAjHLz8g==
-----END RSA PRIVATE KEY-----
KEY
let(:parent_key) { OpenSSL::PKey::RSA.new(parent_serialized_key) }
# -----BEGIN PUBLIC KEY-----
# MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrOvW1UArKoUOg54XWXcTD3jU0
# zKG3Pm9IeaEzfQtApogQ3+M/F9nz0i3q8UhTDEPBQ3hMbqJ/4qfY+wFulxMR58Db
# qxFx9QcNZISUd0CPx/fJOYMxR7bygTbiCet4FAiyMjxOX3Oei/DedUNps1RAP1bu
# +80iibze/Kk9BgMm0QIDAQAB
# -----END PUBLIC KEY-----
let(:author) { "alice@pod-a.org" }
let(:guid) { "e21589b0b41101333b870f77ba60fa73" }
let(:parent_guid) { "9e269ae0b41201333b8c0f77ba60fa73" }
@ -65,31 +40,16 @@ module DiasporaFederation
)
}
let(:legacy_format_comment_xml_alice) { <<~XML }
<XML>
<post>
<comment>
<guid>e21589b0b41101333b870f77ba60fa73</guid>
<parent_guid>9e269ae0b41201333b8c0f77ba60fa73</parent_guid>
<author_signature>XU5X1uqTh8SY6JMG9uhEVR5Rg7FURV6lpRwl/HYOu6DJ3Hd9tpA2aSFFibUxxsMgJXKNrrc5SykrrEdTiQoEei+j0QqZf3B5R7r84qgK7M46KazwIpqRPwVl2MdA/0DdQyYJLA/oavNj1nwll9vtR87M7e/C94qG6P+iQTMBQzo=</author_signature>
<parent_author_signature/>
<text>this is a very informative comment</text>
<diaspora_handle>alice@pod-a.org</diaspora_handle>
</comment>
</post>
</XML>
XML
let(:new_format_comment_xml_alice) { <<~XML }
let(:comment_xml_alice) { <<~XML }
<comment>
<author>alice@pod-a.org</author>
<guid>e21589b0b41101333b870f77ba60fa73</guid>
<parent_guid>9e269ae0b41201333b8c0f77ba60fa73</parent_guid>
<text>this is a very informative comment</text>
<author_signature>SQbLeqsEpFmSl74G1fFJXKQcsq6jp5B2ZjmfEOF/LbBccYP2oZEyEqOq18K3Fa71OYTp6Nddb38hCmHWWHvnGUltGfxKBnQ0WHafJUi40VM4VmeRoU8cac6m+1hslwe5SNmK6oh47EV3mRCXlgGGjLIrw7iEwjKL2g9x1gkNp2s=</author_signature>
<parent_author_signature/>
</comment>
XML
let(:new_data_comment_xml_alice) { <<~XML }
let(:comment_xml_alice_with_new_data) { <<~XML }
<comment>
<author>alice@pod-a.org</author>
<guid>e21589b0b41101333b870f77ba60fa73</guid>
@ -97,74 +57,28 @@ module DiasporaFederation
<text>this is a very informative comment</text>
<new_data>foobar</new_data>
<author_signature>SFYXSvCX/DhTFiOUALp2Nf1kfNkGKXrnoBPikAyhaIogGydVBm+8tIlu1U/vsnpyKO3yfC3JReJ00/UBd4J16VO1IxStntq8NUqbSv4me5A/6kdK9Xg6eYbXrqQGm8fUQ5Xuh2UzeB71p7SVySXX3OZHVe0dvHCxH/lsfSDpEjc=</author_signature>
<parent_author_signature/>
</comment>
XML
let(:legacy_format_comment_xml_bob) { <<~XML }
<XML>
<post>
<comment>
<guid>e21589b0b41101333b870f77ba60fa73</guid>
<parent_guid>9e269ae0b41201333b8c0f77ba60fa73</parent_guid>
<text>this is a very informative comment</text>
<diaspora_handle>alice@pod-a.org</diaspora_handle>
<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>
</post>
</XML>
XML
let(:legacy_order_new_format_comment_xml_bob) { <<~XML }
let(:comment_xml_bob_legacy_order) { <<~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>
<diaspora_handle>alice@pod-a.org</diaspora_handle>
<guid>e21589b0b41101333b870f77ba60fa73</guid>
<parent_guid>9e269ae0b41201333b8c0f77ba60fa73</parent_guid>
<text>this is a very informative comment</text>
<author_signature>SQbLeqsEpFmSl74G1fFJXKQcsq6jp5B2ZjmfEOF/LbBccYP2oZEyEqOq18K3Fa71OYTp6Nddb38hCmHWWHvnGUltGfxKBnQ0WHafJUi40VM4VmeRoU8cac6m+1hslwe5SNmK6oh47EV3mRCXlgGGjLIrw7iEwjKL2g9x1gkNp2s=</author_signature>
<parent_author_signature>hWsagsczmZD6d36d6MFdTt3hKAdnRtupSIU6464G2kkMJ+WlExxMgbF6kWR+jVCBTeKipWCYK3Arnj0YkuIZM9d14bJGVMTsW/ZzNfJ69bXZhsyawI8dPnZnLVydo+hU/XmGJBEuh2TOj9Emq6/HCYiWzPTF5qhYAtyJ1oxJ4Yk=</parent_author_signature>
</comment>
</post>
</XML>
XML
let(:new_format_comment_xml_bob) { <<~XML }
let(:comment_xml_bob) { <<~XML }
<comment>
<author>alice@pod-a.org</author>
<guid>e21589b0b41101333b870f77ba60fa73</guid>
<parent_guid>9e269ae0b41201333b8c0f77ba60fa73</parent_guid>
<text>this is a very informative comment</text>
<author_signature>SQbLeqsEpFmSl74G1fFJXKQcsq6jp5B2ZjmfEOF/LbBccYP2oZEyEqOq18K3Fa71OYTp6Nddb38hCmHWWHvnGUltGfxKBnQ0WHafJUi40VM4VmeRoU8cac6m+1hslwe5SNmK6oh47EV3mRCXlgGGjLIrw7iEwjKL2g9x1gkNp2s=</author_signature>
<parent_author_signature>hWsagsczmZD6d36d6MFdTt3hKAdnRtupSIU6464G2kkMJ+WlExxMgbF6kWR+jVCBTeKipWCYK3Arnj0YkuIZM9d14bJGVMTsW/ZzNfJ69bXZhsyawI8dPnZnLVydo+hU/XmGJBEuh2TOj9Emq6/HCYiWzPTF5qhYAtyJ1oxJ4Yk=</parent_author_signature>
</comment>
XML
let(:legacy_format_new_data_comment_xml_bob) { <<~XML }
<XML>
<post>
<comment>
<diaspora_handle>alice@pod-a.org</diaspora_handle>
<guid>e21589b0b41101333b870f77ba60fa73</guid>
<parent_guid>9e269ae0b41201333b8c0f77ba60fa73</parent_guid>
<text>this is a very informative comment</text>
<new_data>foobar</new_data>
<author_signature>SFYXSvCX/DhTFiOUALp2Nf1kfNkGKXrnoBPikAyhaIogGydVBm+8tIlu1U/vsnpyKO3yfC3JReJ00/UBd4J16VO1IxStntq8NUqbSv4me5A/6kdK9Xg6eYbXrqQGm8fUQ5Xuh2UzeB71p7SVySXX3OZHVe0dvHCxH/lsfSDpEjc=</author_signature>
<parent_author_signature>NxXuEUVeXwUMR77osIbaNlp2oB3bpl8rBEFgQoO6cnoN5ewDbiGADK0x6EhcmJptjwhGVcZiNJNpq7k3/pjJtKaH++3ToCAtcuZoIKwPDsneLnjPhVjE2GXM1TiZKwoHrq41qSp/8Vl5UPbtC6sPiOzIvPKaILXUG8XCiVWuB0M=</parent_author_signature>
</comment>
</post>
</XML>
XML
let(:new_data_comment_xml_bob) { <<~XML }
let(:comment_xml_bob_with_new_data) { <<~XML }
<comment>
<author>alice@pod-a.org</author>
<guid>e21589b0b41101333b870f77ba60fa73</guid>
@ -172,7 +86,6 @@ module DiasporaFederation
<text>this is a very informative comment</text>
<new_data>foobar</new_data>
<author_signature>SFYXSvCX/DhTFiOUALp2Nf1kfNkGKXrnoBPikAyhaIogGydVBm+8tIlu1U/vsnpyKO3yfC3JReJ00/UBd4J16VO1IxStntq8NUqbSv4me5A/6kdK9Xg6eYbXrqQGm8fUQ5Xuh2UzeB71p7SVySXX3OZHVe0dvHCxH/lsfSDpEjc=</author_signature>
<parent_author_signature>NxXuEUVeXwUMR77osIbaNlp2oB3bpl8rBEFgQoO6cnoN5ewDbiGADK0x6EhcmJptjwhGVcZiNJNpq7k3/pjJtKaH++3ToCAtcuZoIKwPDsneLnjPhVjE2GXM1TiZKwoHrq41qSp/8Vl5UPbtC6sPiOzIvPKaILXUG8XCiVWuB0M=</parent_author_signature>
</comment>
XML
@ -180,44 +93,35 @@ module DiasporaFederation
context "test-data creation" do
it "creates comment xml" do
expect_callback(:fetch_private_key, author).and_return(author_key)
expect_callback(:fetch_private_key, parent.author).and_return(nil)
comment.to_xml
end
it "creates relayed comment xml" do
expect_callback(:fetch_public_key, author).and_return(author_key.public_key)
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(new_data_comment_xml_alice).root
Salmon::XmlPayload.unpack(xml).to_xml
xml = Nokogiri::XML(comment_xml_alice_with_new_data).root
Entity.entity_class(xml.name).from_xml(xml).to_xml
end
end
context "relaying on bobs pod" do
before do
expect_callback(:fetch_public_key, author).and_return(author_key.public_key)
expect_callback(:fetch_private_key, parent.author).and_return(parent_key)
expect_callback(:fetch_related_entity, "Post", parent_guid).and_return(parent)
end
it "relays legacy order" do
xml = Nokogiri::XML(legacy_format_comment_xml_alice).root
entity = Salmon::XmlPayload.unpack(xml)
expect(entity.to_xml.to_xml).to eq(legacy_order_new_format_comment_xml_bob.strip)
end
it "relays new order" do
xml = Nokogiri::XML(new_format_comment_xml_alice).root
entity = Salmon::XmlPayload.unpack(xml)
expect(entity.to_xml.to_xml).to eq(new_format_comment_xml_bob.strip)
xml = Nokogiri::XML(comment_xml_alice).root
entity = Entity.entity_class(xml.name).from_xml(xml)
expect(entity.to_xml.to_xml).to eq(comment_xml_bob.strip)
end
it "relays new data" do
xml = Nokogiri::XML(new_data_comment_xml_alice).root
entity = Salmon::XmlPayload.unpack(xml)
expect(entity.to_xml.to_xml).to eq(new_data_comment_xml_bob.strip)
xml = Nokogiri::XML(comment_xml_alice_with_new_data).root
entity = Entity.entity_class(xml.name).from_xml(xml)
expect(entity.to_xml.to_xml).to eq(comment_xml_bob_with_new_data.strip)
end
end
@ -229,50 +133,25 @@ module DiasporaFederation
expect_callback(:fetch_related_entity, "Post", parent_guid).and_return(parent)
end
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 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 order with legacy xml format" do
xml = Nokogiri::XML(new_order_legacy_format_comment_xml_bob).root
entity = Salmon::XmlPayload.unpack(xml)
xml = Nokogiri::XML(comment_xml_bob_legacy_order).root
entity = Entity.entity_class(xml.name).from_xml(xml)
expect(entity.author).to eq(author)
expect(entity.text).to eq(text)
end
it "parses new xml format" do
xml = Nokogiri::XML(new_format_comment_xml_bob).root
entity = Salmon::XmlPayload.unpack(xml)
xml = Nokogiri::XML(comment_xml_bob).root
entity = Entity.entity_class(xml.name).from_xml(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)
expect(entity.text).to eq(text)
expect(entity.additional_data["new_data"]).to eq(new_data)
end
it "parses new data with new xml format" do
xml = Nokogiri::XML(new_data_comment_xml_bob).root
entity = Salmon::XmlPayload.unpack(xml)
xml = Nokogiri::XML(comment_xml_bob_with_new_data).root
entity = Entity.entity_class(xml.name).from_xml(xml)
expect(entity.author).to eq(author)
expect(entity.text).to eq(text)

View file

@ -215,7 +215,8 @@ module DiasporaFederation
it "fails validation on parsing" do
expect {
DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML(xml).root)
parsed_xml = Nokogiri::XML(xml).root
Entity.entity_class(parsed_xml.name).from_xml(parsed_xml)
}.to raise_error Entity::ValidationError
end
end

View file

@ -23,7 +23,6 @@ module DiasporaFederation
<created_at>#{data[:created_at].utc.iso8601}</created_at>
<edited_at>#{data[:edited_at].utc.iso8601}</edited_at>
<author_signature>#{data[:author_signature]}</author_signature>
<parent_author_signature>#{data[:parent_author_signature]}</parent_author_signature>
</comment>
XML

View file

@ -54,11 +54,12 @@ module DiasporaFederation
<guid>#{parent.guid}</guid>
<subject>#{data[:subject]}</subject>
<created_at>#{data[:created_at]}</created_at>
<participant_handles>#{data[:participants]}</participant_handles>
<participants>#{data[:participants]}</participants>
</conversation>
XML
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML(minimal_xml).root)
parsed_xml = Nokogiri::XML(minimal_xml).root
parsed_instance = Entity.entity_class(parsed_xml.name).from_xml(parsed_xml)
expect(parsed_instance.messages).to eq([])
end
end

View file

@ -21,7 +21,6 @@ module DiasporaFederation
<status>#{data[:status]}</status>
<edited_at>#{data[:edited_at].utc.iso8601}</edited_at>
<author_signature>#{data[:author_signature]}</author_signature>
<parent_author_signature>#{data[:parent_author_signature]}</parent_author_signature>
</event_participation>
XML

View file

@ -43,7 +43,8 @@ module DiasporaFederation
</event>
XML
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML(minimal_xml).root)
parsed_xml = Nokogiri::XML(minimal_xml).root
parsed_instance = Entity.entity_class(parsed_xml.name).from_xml(parsed_xml)
expect(parsed_instance.end).to be_nil
expect(parsed_instance.all_day).to be_falsey
expect(parsed_instance.timezone).to be_nil

View file

@ -22,7 +22,6 @@ module DiasporaFederation
<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>
XML
@ -75,7 +74,7 @@ module DiasporaFederation
it "raises a ValidationError if the parent_guid is missing" do
broken_xml = <<~XML
<like>
<target_type>#{parent.entity_type}</target_type>
<parent_type>#{parent.entity_type}</parent_type>
</like>
XML

View file

@ -62,7 +62,8 @@ module DiasporaFederation
</photo>
XML
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML(minimal_xml).root)
parsed_xml = Nokogiri::XML(minimal_xml).root
parsed_instance = Entity.entity_class(parsed_xml.name).from_xml(parsed_xml)
expect(parsed_instance.public).to be_falsey
expect(parsed_instance.text).to be_nil
end

View file

@ -20,7 +20,6 @@ module DiasporaFederation
<parent_guid>#{parent.guid}</parent_guid>
<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>
</poll_participation>
XML

View file

@ -63,7 +63,8 @@ module DiasporaFederation
</profile>
XML
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML(minimal_xml).root)
parsed_xml = Nokogiri::XML(minimal_xml).root
parsed_instance = Entity.entity_class(parsed_xml.name).from_xml(parsed_xml)
expect(parsed_instance.full_name).to be_nil
expect(parsed_instance.first_name).to be_nil
expect(parsed_instance.last_name).to be_nil

View file

@ -1,46 +0,0 @@
# frozen_string_literal: true
module DiasporaFederation
describe Entities::RelayableRetraction do
let(:target) { Fabricate(:comment, author: bob) }
let(:target_entity) {
Fabricate(
:related_entity,
author: bob.diaspora_id,
parent: Fabricate(:related_entity, author: alice.diaspora_id)
)
}
let(:data) { {author: alice.diaspora_id, target_guid: target.guid, target_type: target.entity_type} }
let(:xml) { <<~XML }
<relayable_retraction>
<parent_author_signature/>
<target_guid>#{data[:target_guid]}</target_guid>
<target_type>#{data[:target_type]}</target_type>
<sender_handle>#{data[:author]}</sender_handle>
<target_author_signature/>
</relayable_retraction>
XML
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
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
end

View file

@ -3,7 +3,6 @@
module DiasporaFederation
describe Entities::Relayable do
let(:author_pkey) { OpenSSL::PKey::RSA.generate(1024) }
let(:parent_pkey) { OpenSSL::PKey::RSA.generate(1024) }
let(:guid) { Fabricate.sequence(:guid) }
let(:parent_guid) { Fabricate.sequence(:guid) }
@ -13,24 +12,32 @@ module DiasporaFederation
let(:local_parent) { Fabricate(:related_entity, author: bob.diaspora_id) }
let(:remote_parent) { Fabricate(:related_entity, author: bob.diaspora_id, local: false) }
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(:hash_with_fake_signatures) { hash.merge!(author_signature: "aa") }
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
signature_order = [:author, :guid, :parent_guid, :property, "new_property", :author_signature]
signature_order =
[:author, :guid, :parent_guid, :property, "new_property", :author_signature, "parent_author_signature"]
expect(Entities::SomeRelayable.new(hash, signature_order).signature_order)
.to eq([:author, :guid, :parent_guid, :property, "new_property"])
end
it "filters signatures from additional_data" do
signature_order = [:author, :guid, :parent_guid, :property, "new_property"]
additional_data = {"new_property" => "foobar", "parent_author_signature" => "bb"}
expect(Entities::SomeRelayable.new(hash, signature_order, additional_data).additional_data)
.to eq("new_property" => "foobar")
end
end
describe "#verify_signature" do
it "doesn't raise anything if correct signatures were passed" do
it "doesn't raise anything if correct author_signature was 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)
@ -38,12 +45,11 @@ module DiasporaFederation
expect { Entities::SomeRelayable.new(hash, signature_order).verify_signature }.not_to raise_error
end
it "doesn't raise anything if correct signatures with new property were passed" do
it "doesn't raise anything if correct author_signature with new property was 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)
@ -99,26 +105,6 @@ module DiasporaFederation
Entities::SomeRelayable.new(hash, signature_order).verify_signature
}.to raise_error Entities::Relayable::SignatureVerificationFailed
end
it "doesn't raise 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
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect { Entities::SomeRelayable.new(hash, signature_order).verify_signature }.not_to raise_error
end
it "doesn't raise when no parent author signature was passed and we're on upstream federation" do
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, signature_order).verify_signature }.not_to raise_error
end
end
describe "#to_xml" do
@ -130,7 +116,6 @@ module DiasporaFederation
<property>#{property}</property>
<new_property>#{new_property}</new_property>
<author_signature>aa</author_signature>
<parent_author_signature>bb</parent_author_signature>
</some_relayable>
XML
@ -169,7 +154,6 @@ module DiasporaFederation
<property/>
<new_property>#{new_property}</new_property>
<author_signature>aa</author_signature>
<parent_author_signature>bb</parent_author_signature>
</some_relayable>
XML
@ -188,7 +172,6 @@ module DiasporaFederation
<guid>#{guid}</guid>
<parent_guid>#{parent_guid}</parent_guid>
<author_signature>aa</author_signature>
<parent_author_signature>bb</parent_author_signature>
</some_relayable>
XML
@ -197,22 +180,16 @@ module DiasporaFederation
expect(xml.to_s.strip).to eq(expected_xml.strip)
end
it "computes correct signatures for the entity" do
it "computes correct author_signature for the entity" do
expect_callback(:fetch_private_key, author).and_return(author_pkey)
expect_callback(:fetch_private_key, local_parent.author).and_return(parent_pkey)
xml = Entities::SomeRelayable.new(hash).to_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, signature_data)).to be_truthy
expect(verify_signature(parent_pkey, parent_author_signature, signature_data)).to be_truthy
expect(verify_signature(author_pkey, xml.at_xpath("author_signature").text, signature_data)).to be_truthy
end
it "computes correct signatures for the entity with invalid XML characters" do
it "computes correct author_signature for the entity with invalid XML characters" do
expect_callback(:fetch_private_key, author).and_return(author_pkey)
expect_callback(:fetch_private_key, local_parent.author).and_return(parent_pkey)
invalid_property = "asdfasdf asdf💩asdf\nasdf"
signature_data_with_fixed_property = "#{author};#{guid};#{parent_guid};asdf<64>asdf asdf💩asdf\nasdf"
@ -220,31 +197,22 @@ module DiasporaFederation
xml = Entities::SomeRelayable.new(hash.merge(property: invalid_property)).to_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, signature_data_with_fixed_property)).to be_truthy
expect(verify_signature(parent_pkey, parent_author_signature, signature_data_with_fixed_property)).to be_truthy
end
it "computes correct signatures for the entity when the parent is a relayable itself" do
it "computes correct author_signature for the entity when the parent is a relayable itself" do
intermediate_author = Fabricate.sequence(:diaspora_id)
parent = Fabricate(:related_entity, author: intermediate_author, local: true, parent: local_parent)
expect_callback(:fetch_private_key, author).and_return(author_pkey)
expect_callback(:fetch_private_key, local_parent.author).and_return(parent_pkey)
expect(DiasporaFederation.callbacks).not_to receive(:trigger).with(:fetch_private_key, intermediate_author)
xml = Entities::SomeRelayable.new(hash.merge(parent: parent)).to_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, signature_data)).to be_truthy
expect(verify_signature(parent_pkey, parent_author_signature, signature_data)).to be_truthy
expect(verify_signature(author_pkey, xml.at_xpath("author_signature").text, signature_data)).to be_truthy
end
it "computes correct signatures for the entity with new unknown xml elements" do
it "computes correct author_signature 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)
signature_order = [:author, :guid, :parent_guid, "new_property", :property]
signature_data_with_new_property = "#{author};#{guid};#{parent_guid};#{new_property};#{property}"
@ -252,17 +220,13 @@ module DiasporaFederation
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
expect(verify_signature(author_pkey, author_signature, signature_data_with_new_property)).to be_truthy
expect(verify_signature(parent_pkey, parent_author_signature, signature_data_with_new_property)).to be_truthy
end
it "doesn't change signatures if they are already set" do
it "doesn't change author_signature if it is already set" do
xml = Entities::SomeRelayable.new(hash_with_fake_signatures).to_xml
expect(xml.at_xpath("author_signature").text).to eq("aa")
expect(xml.at_xpath("parent_author_signature").text).to eq("bb")
end
it "raises when author_signature not set and key isn't supplied" do
@ -273,15 +237,6 @@ module DiasporaFederation
}.to raise_error Entities::Relayable::AuthorPrivateKeyNotFound
end
it "doesn't set parent_author_signature if key isn't supplied" do
expect_callback(:fetch_private_key, author).and_return(author_pkey)
expect_callback(:fetch_private_key, local_parent.author).and_return(nil)
xml = Entities::SomeRelayable.new(hash).to_xml
expect(xml.at_xpath("parent_author_signature").text).to eq("")
end
it "adds 'false' booleans" do
expected_xml = <<~XML
<test_relayable_with_boolean>
@ -290,7 +245,6 @@ module DiasporaFederation
<parent_guid>#{parent_guid}</parent_guid>
<test>false</test>
<author_signature>aa</author_signature>
<parent_author_signature>bb</parent_author_signature>
</test_relayable_with_boolean>
XML
@ -310,13 +264,12 @@ module DiasporaFederation
let(:new_signature_data) { "#{author};#{guid};#{parent_guid};#{new_property};#{property}" }
let(:new_xml) { <<~XML }
<some_relayable>
<diaspora_handle>#{author}</diaspora_handle>
<author>#{author}</author>
<guid>#{guid}</guid>
<parent_guid>#{parent_guid}</parent_guid>
<new_property>#{new_property}</new_property>
<property>#{property}</property>
<author_signature>#{sign_with_key(author_pkey, new_signature_data)}</author_signature>
<parent_author_signature>#{sign_with_key(parent_pkey, new_signature_data)}</parent_author_signature>
</some_relayable>
XML
@ -338,7 +291,6 @@ module DiasporaFederation
it "creates Entity with empty 'additional_data' if the xml has only known properties" do
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
@ -450,13 +402,6 @@ module DiasporaFederation
Entities::SomeRelayable.new(hash).to_json
}.to raise_error Entities::Relayable::AuthorPrivateKeyNotFound
end
it "doesn't contain the parent_author_signature" do
expect_callback(:fetch_private_key, author).and_return(author_pkey)
json = Entities::SomeRelayable.new(hash).to_json
expect(json[:entity_data]).not_to include(:parent_author_signature)
end
end
describe ".from_hash" do
@ -471,16 +416,14 @@ module DiasporaFederation
context "when properties are sorted and there is an unknown property" do
let(:new_signature_data) { "#{author};#{guid};#{parent_guid};#{new_property};#{property}" }
let(:author_signature) { sign_with_key(author_pkey, new_signature_data) }
let(:parent_author_signature) { sign_with_key(parent_pkey, new_signature_data) }
let(:entity_data) {
{
:guid => guid,
:author => author,
:property => property,
:parent_guid => parent_guid,
"new_property" => new_property,
:author_signature => author_signature,
:parent_author_signature => parent_author_signature
:guid => guid,
:author => author,
:property => property,
:parent_guid => parent_guid,
"new_property" => new_property,
:author_signature => author_signature
}
}
let(:property_order) { %w[author guid parent_guid new_property property] }
@ -493,7 +436,6 @@ module DiasporaFederation
expect(entity.parent_guid).to eq(parent_guid)
expect(entity.property).to eq(property)
expect(entity.author_signature).to eq(author_signature)
expect(entity.parent_author_signature).to eq(parent_author_signature)
end
it "makes unknown properties available via #additional_data" do
@ -509,13 +451,12 @@ module DiasporaFederation
it "calls a constructor of the entity of the appropriate type" do
expect(Entities::SomeRelayable).to receive(:new).with(
{
author: author,
guid: guid,
parent_guid: parent_guid,
property: property,
author_signature: author_signature,
parent_author_signature: parent_author_signature,
parent: remote_parent
author: author,
guid: guid,
parent_guid: parent_guid,
property: property,
author_signature: author_signature,
parent: remote_parent
}.merge("new_property" => new_property),
%w[author guid parent_guid new_property property],
"new_property" => new_property
@ -528,12 +469,11 @@ module DiasporaFederation
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, signature_data),
parent_author_signature: sign_with_key(parent_pkey, signature_data)
guid: guid,
author: author,
property: property,
parent_guid: parent_guid,
author_signature: sign_with_key(author_pkey, signature_data)
}
entity = Entities::SomeRelayable.from_hash(entity_data, property_order)
@ -547,12 +487,11 @@ module DiasporaFederation
it "calls signatures verification on relayable unpack" do
property_order = %w[guid author property parent_guid]
entity_data = {
guid: guid,
author: author,
property: property,
parent_guid: parent_guid,
author_signature: "aa",
parent_author_signature: "bb"
guid: guid,
author: author,
property: property,
parent_guid: parent_guid,
author_signature: "aa"
}
expect_callback(:fetch_related_entity, "Parent", parent_guid).and_return(remote_parent)

View file

@ -1,33 +0,0 @@
# frozen_string_literal: true
module DiasporaFederation
describe Entities::Request do
let(:data) { {author: Fabricate.sequence(:diaspora_id), recipient: Fabricate.sequence(:diaspora_id)} }
let(:xml) { <<~XML }
<request>
<sender_handle>#{data[:author]}</sender_handle>
<recipient_handle>#{data[:recipient]}</recipient_handle>
</request>
XML
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(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
end

View file

@ -1,39 +0,0 @@
# frozen_string_literal: true
module DiasporaFederation
describe Entities::SignedRetraction do
let(:target) { Fabricate(:post, author: alice) }
let(:target_entity) { Fabricate(:related_entity, author: alice.diaspora_id) }
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/>
</signed_retraction>
XML
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
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
end

View file

@ -136,7 +136,8 @@ module DiasporaFederation
</status_message>
XML
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(Nokogiri::XML(minimal_xml).root)
parsed_xml = Nokogiri::XML(minimal_xml).root
parsed_instance = Entity.entity_class(parsed_xml.name).from_xml(parsed_xml)
expect(parsed_instance.photos).to eq([])
expect(parsed_instance.location).to be_nil
expect(parsed_instance.poll).to be_nil

View file

@ -528,14 +528,5 @@ module DiasporaFederation
expect(entity.multi).to be_empty
end
end
context "xml_name" do
let(:hash) { {test: "test", qwer: "qwer"} }
it "should not use the xml_name for the #to_h" do
entity = Entities::TestEntityWithXmlName.new(hash)
expect(entity.to_h).to eq(hash)
end
end
end
end

View file

@ -23,21 +23,6 @@ module DiasporaFederation
described_class.receive_public(data)
end
it "parses the entity with legacy slap receiver" do
expect_callback(:fetch_public_key, post.author).and_return(sender_key)
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)
expect(entity.author).to eq(post.author)
expect(entity.text).to eq(post.text)
expect(entity.public).to eq("true")
end
described_class.receive_public(data, true)
end
it "redirects exceptions from the receiver" do
expect {
described_class.receive_public("<xml/>")
@ -64,21 +49,6 @@ module DiasporaFederation
described_class.receive_private(data, recipient_key, 1234)
end
it "parses the entity with legacy slap receiver" do
expect_callback(:fetch_public_key, post.author).and_return(sender_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)
expect(entity.author).to eq(post.author)
expect(entity.text).to eq(post.text)
expect(entity.public).to eq("false")
end
described_class.receive_private(data, recipient_key, 1234, true)
end
it "raises when recipient private key is not available" do
magic_env = Salmon::MagicEnvelope.new(post, post.author).envelop(sender_key)
data = Salmon::EncryptedMagicEnvelope.encrypt(magic_env, recipient_key.public_key)

View file

@ -33,34 +33,6 @@ module DiasporaFederation
end
end
it "uses xml_name for parsing" do
xml = <<~XML.strip
<test_entity_with_xml_name>
<test>asdf</test>
<asdf>qwer</asdf>
</test_entity_with_xml_name>
XML
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")
end
it "allows name for parsing even when property has a xml_name" do
xml = <<~XML.strip
<test_entity_with_xml_name>
<test>asdf</test>
<qwer>qwer</qwer>
</test_entity_with_xml_name>
XML
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")
end
it "parses the string to the correct type" do
xml = <<~XML.strip
<test_default_entity>

View file

@ -10,7 +10,6 @@ module DiasporaFederation
properties = dsl.class_props
expect(properties).to have(1).item
expect(properties[:test]).to eq(:string)
expect(dsl.xml_names[:test]).to eq(:test)
end
it "will not accept other types for names" do
@ -46,22 +45,6 @@ module DiasporaFederation
expect(properties.keys).to include(:test, :asdf, :zzzz)
properties.each_value {|type| expect(type).to eq(:string) }
end
it "can add an xml name to simple properties with a symbol" do
dsl.property :test, :string, xml_name: :xml_test
properties = dsl.class_props
expect(properties).to have(1).item
expect(properties[:test]).to eq(:string)
expect(dsl.xml_names[:test]).to eq(:xml_test)
end
it "will not accept other types for xml names" do
["test", 1234, true, {}].each do |val|
expect {
dsl.property :test, :string, xml_name: val
}.to raise_error PropertiesDSL::InvalidName, "invalid xml_name"
end
end
end
context "nested entities" do
@ -99,12 +82,6 @@ module DiasporaFederation
}.to raise_error PropertiesDSL::InvalidType
end
end
it "can not add an xml name to a nested entity" do
expect {
dsl.entity :other, Entities::TestEntity, xml_name: :other_name
}.to raise_error ArgumentError, "xml_name is not supported for nested entities"
end
end
describe ".optional_props" do
@ -174,22 +151,5 @@ module DiasporaFederation
expect(data).not_to have_key(:test_alias)
end
end
describe ".find_property_for_xml_name" do
it "finds property by xml_name" do
dsl.property :test, :string, xml_name: :xml_test
expect(dsl.find_property_for_xml_name("xml_test")).to eq(:test)
end
it "finds property by name" do
dsl.property :test, :string, xml_name: :xml_test
expect(dsl.find_property_for_xml_name("test")).to eq(:test)
end
it "returns nil if property is not defined" do
dsl.property :test, :string, xml_name: :xml_test
expect(dsl.find_property_for_xml_name("unknown")).to be_nil
end
end
end
end

View file

@ -1,59 +0,0 @@
# frozen_string_literal: true
module DiasporaFederation
describe Salmon::EncryptedSlap do
let(:sender) { "user_test@diaspora.example.tld" }
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) { generate_legacy_encrypted_salmon_slap(payload, sender, privkey, recipient_key.public_key) }
describe ".from_xml" do
context "sanity" do
it "accepts correct params" do
expect_callback(:fetch_public_key, sender).and_return(privkey.public_key)
expect {
Salmon::EncryptedSlap.from_xml(slap_xml, recipient_key)
}.not_to raise_error
end
it "raises an error when the params have a wrong type" do
[1234, false, :symbol, payload, privkey].each do |val|
expect {
Salmon::EncryptedSlap.from_xml(val, val)
}.to raise_error ArgumentError
end
end
it "verifies the existence of 'encrypted_header'" do
faulty_xml = <<~XML
<diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env">
</diaspora>
XML
expect {
Salmon::EncryptedSlap.from_xml(faulty_xml, recipient_key)
}.to raise_error Salmon::MissingHeader
end
it "verifies the existence of a magic envelope" do
faulty_xml = <<~XML
<diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env">
<encrypted_header/>
</diaspora>
XML
expect(Salmon::EncryptedSlap).to receive(:header_data).and_return(aes_key: "", iv: "", author_id: "")
expect {
Salmon::EncryptedSlap.from_xml(faulty_xml, recipient_key)
}.to raise_error Salmon::MissingMagicEnvelope
end
end
context "generated instance" do
it_behaves_like "a MagicEnvelope instance" do
subject { Salmon::EncryptedSlap.from_xml(slap_xml, recipient_key) }
end
end
end
end
end

View file

@ -16,6 +16,14 @@ module DiasporaFederation
[data, type, enc, alg].map {|i| Base64.urlsafe_encode64(i) }.join(".")
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
context "sanity" do
it "constructs an instance" do
expect {

View file

@ -1,60 +0,0 @@
# frozen_string_literal: true
module DiasporaFederation
describe Salmon::Slap do
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) { generate_legacy_salmon_slap(payload, sender, privkey) }
describe ".from_xml" do
context "sanity" do
it "accepts salmon xml as param" do
expect_callback(:fetch_public_key, sender).and_return(privkey.public_key)
expect {
Salmon::Slap.from_xml(slap_xml)
}.not_to raise_error
end
it "raises an error when the param has a wrong type" do
[1234, false, :symbol, payload, privkey].each do |val|
expect {
Salmon::Slap.from_xml(val)
}.to raise_error ArgumentError
end
end
it "verifies the existence of an author_id" do
faulty_xml = <<~XML
<diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env">
<header/>
</diaspora>
XML
expect {
Salmon::Slap.from_xml(faulty_xml)
}.to raise_error Salmon::MissingAuthor
end
it "verifies the existence of a magic envelope" do
faulty_xml = <<~XML
<diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env">
<header>
<author_id>#{sender}</author_id>
</header>
</diaspora>
XML
expect {
Salmon::Slap.from_xml(faulty_xml)
}.to raise_error Salmon::MissingMagicEnvelope
end
end
context "generated instance" do
it_behaves_like "a MagicEnvelope instance" do
subject { Salmon::Slap.from_xml(slap_xml) }
end
end
end
end
end

View file

@ -1,60 +0,0 @@
# frozen_string_literal: true
module DiasporaFederation
describe Salmon::XmlPayload do
let(:entity) { Entities::TestEntity.new(test: "asdf") }
let(:entity_xml) { <<~XML.strip }
<XML>
<post>
<test_entity>
<test>asdf</test>
</test_entity>
</post>
</XML>
XML
describe ".unpack" do
context "sanity" do
it "expects an Nokogiri::XML::Element as param" do
expect {
Salmon::XmlPayload.unpack(Nokogiri::XML(entity_xml).root)
}.not_to raise_error
end
it "raises and error when the param is not an Nokogiri::XML::Element" do
["asdf", 1234, true, :test, entity].each do |val|
expect {
Salmon::XmlPayload.unpack(val)
}.to raise_error ArgumentError, "only Nokogiri::XML::Element allowed"
end
end
end
context "returned object" do
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)
end
it "returns an entity instance of the original class" do
expect(subject).to be_an_instance_of Entities::TestEntity
expect(subject.test).to eq("asdf")
end
it "allows unwrapped entities" do
xml = <<~XML
<test_entity>
<test>asdf</test>
</test_entity>
XML
entity = Salmon::XmlPayload.unpack(Nokogiri::XML(xml).root)
expect(entity).to be_an_instance_of Entities::TestEntity
expect(entity.test).to eq("asdf")
end
end
end
end
end

View file

@ -18,7 +18,6 @@ end
def add_signatures(hash, klass=described_class)
properties = klass.new(hash).send(:xml_elements)
hash[:author_signature] = properties[:author_signature]
hash[:parent_author_signature] = properties[:parent_author_signature]
end
def sign_with_key(privkey, signature_data)

View file

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

View file

@ -1,7 +1,6 @@
# frozen_string_literal: true
def entity_hash_from(hash)
hash.delete(:parent_author_signature)
hash.map {|key, value|
if [String, TrueClass, FalseClass, Integer, NilClass].any? {|c| value.is_a? c }
[key, value]
@ -67,7 +66,7 @@ shared_examples "an XML Entity" do |ignored_props=[]|
context "parsing" do
it "reads its own output" do
packed_xml = instance.to_xml
parsed_instance = DiasporaFederation::Salmon::XmlPayload.unpack(packed_xml)
parsed_instance = DiasporaFederation::Entity.entity_class(packed_xml.name).from_xml(packed_xml)
check_entity(instance, parsed_instance, ignored_props)
end
@ -101,24 +100,19 @@ shared_examples "an XML Entity" do |ignored_props=[]|
end
shared_examples "a relayable Entity" do
let(:instance) { described_class.new(data.merge(author_signature: nil, parent_author_signature: nil)) }
let(:instance) { described_class.new(data.merge(author_signature: nil)) }
context "signatures generation" do
def verify_signature(pubkey, signature, signed_string)
pubkey.verify(OpenSSL::Digest::SHA256.new, Base64.decode64(signature), signed_string)
end
it "computes correct signatures for the entity" do
order = described_class.class_props.keys - %i[author_signature parent_author_signature parent]
it "computes correct author_signature for the entity" do
order = described_class.class_props.keys - %i[author_signature parent]
signed_string = order.map {|name| data[name].is_a?(Time) ? data[name].iso8601 : data[name] }.join(";")
xml = instance.to_xml
author_signature = xml.at_xpath("author_signature").text
parent_author_signature = xml.at_xpath("parent_author_signature").text
author_signature = instance.to_xml.at_xpath("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
end
end
end
@ -182,7 +176,6 @@ shared_examples "a relayable JSON entity" do
it "matches JSON schema with empty string signatures" do
json = described_class.new(data).to_json
json[:entity_data][:author_signature] = ""
json[:entity_data][:parent_author_signature] = ""
expect(json.to_json).to match_json_schema(:entity_schema)
end
end