diaspora_federation 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +34 -0
  3. data/lib/diaspora_federation/discovery/discovery.rb +2 -2
  4. data/lib/diaspora_federation/discovery/h_card.rb +2 -10
  5. data/lib/diaspora_federation/discovery/host_meta.rb +1 -1
  6. data/lib/diaspora_federation/discovery/web_finger.rb +47 -87
  7. data/lib/diaspora_federation/discovery/xrd_document.rb +20 -7
  8. data/lib/diaspora_federation/entities/account_migration.rb +74 -0
  9. data/lib/diaspora_federation/entities/comment.rb +0 -4
  10. data/lib/diaspora_federation/entities/event_participation.rb +0 -8
  11. data/lib/diaspora_federation/entities/like.rb +6 -10
  12. data/lib/diaspora_federation/entities/message.rb +14 -49
  13. data/lib/diaspora_federation/entities/participation.rb +23 -13
  14. data/lib/diaspora_federation/entities/poll_participation.rb +0 -4
  15. data/lib/diaspora_federation/entities/profile.rb +5 -0
  16. data/lib/diaspora_federation/entities/related_entity.rb +17 -0
  17. data/lib/diaspora_federation/entities/relayable.rb +82 -113
  18. data/lib/diaspora_federation/entities/relayable_retraction.rb +4 -43
  19. data/lib/diaspora_federation/entities/request.rb +4 -12
  20. data/lib/diaspora_federation/entities/reshare.rb +11 -7
  21. data/lib/diaspora_federation/entities/retraction.rb +4 -5
  22. data/lib/diaspora_federation/entities/signable.rb +54 -0
  23. data/lib/diaspora_federation/entities/signed_retraction.rb +4 -44
  24. data/lib/diaspora_federation/entities.rb +2 -0
  25. data/lib/diaspora_federation/entity.rb +74 -96
  26. data/lib/diaspora_federation/federation/fetcher.rb +1 -1
  27. data/lib/diaspora_federation/federation/receiver.rb +1 -1
  28. data/lib/diaspora_federation/federation/sender/hydra_wrapper.rb +37 -12
  29. data/lib/diaspora_federation/federation/sender.rb +2 -2
  30. data/lib/diaspora_federation/parsers/base_parser.rb +61 -0
  31. data/lib/diaspora_federation/parsers/json_parser.rb +60 -0
  32. data/lib/diaspora_federation/parsers/relayable_json_parser.rb +25 -0
  33. data/lib/diaspora_federation/parsers/relayable_xml_parser.rb +22 -0
  34. data/lib/diaspora_federation/parsers/xml_parser.rb +84 -0
  35. data/lib/diaspora_federation/parsers.rb +13 -0
  36. data/lib/diaspora_federation/properties_dsl.rb +1 -1
  37. data/lib/diaspora_federation/salmon/encrypted_magic_envelope.rb +1 -1
  38. data/lib/diaspora_federation/salmon/encrypted_slap.rb +2 -99
  39. data/lib/diaspora_federation/salmon/magic_envelope.rb +3 -19
  40. data/lib/diaspora_federation/salmon/slap.rb +1 -42
  41. data/lib/diaspora_federation/salmon/xml_payload.rb +0 -19
  42. data/lib/diaspora_federation/schemas/federation_entities.json +379 -0
  43. data/lib/diaspora_federation/validators/account_deletion_validator.rb +1 -1
  44. data/lib/diaspora_federation/validators/account_migration_validator.rb +12 -0
  45. data/lib/diaspora_federation/validators/contact_validator.rb +2 -2
  46. data/lib/diaspora_federation/validators/conversation_validator.rb +1 -1
  47. data/lib/diaspora_federation/validators/event_validator.rb +1 -1
  48. data/lib/diaspora_federation/validators/h_card_validator.rb +2 -5
  49. data/lib/diaspora_federation/validators/message_validator.rb +1 -1
  50. data/lib/diaspora_federation/validators/participation_validator.rb +1 -1
  51. data/lib/diaspora_federation/validators/person_validator.rb +2 -2
  52. data/lib/diaspora_federation/validators/photo_validator.rb +1 -1
  53. data/lib/diaspora_federation/validators/profile_validator.rb +1 -1
  54. data/lib/diaspora_federation/validators/related_entity_validator.rb +1 -1
  55. data/lib/diaspora_federation/validators/relayable_validator.rb +1 -1
  56. data/lib/diaspora_federation/validators/reshare_validator.rb +2 -2
  57. data/lib/diaspora_federation/validators/retraction_validator.rb +1 -1
  58. data/lib/diaspora_federation/validators/rules/boolean.rb +2 -2
  59. data/lib/diaspora_federation/validators/status_message_validator.rb +1 -1
  60. data/lib/diaspora_federation/validators/web_finger_validator.rb +5 -6
  61. data/lib/diaspora_federation/validators.rb +1 -5
  62. data/lib/diaspora_federation/version.rb +1 -1
  63. data/lib/diaspora_federation.rb +6 -2
  64. metadata +14 -11
  65. data/lib/diaspora_federation/validators/relayable_retraction_validator.rb +0 -15
  66. data/lib/diaspora_federation/validators/request_validator.rb +0 -12
  67. data/lib/diaspora_federation/validators/signed_retraction_validator.rb +0 -15
  68. data/lib/tasks/build.rake +0 -14
  69. data/lib/tasks/diaspora_federation_tasks.rake +0 -4
  70. data/lib/tasks/rails4.rake +0 -15
  71. data/lib/tasks/tests.rake +0 -18
@@ -4,11 +4,22 @@ module DiasporaFederation
4
4
  #
5
5
  # @see Validators::Participation
6
6
  class Participation < Entity
7
- # Old signature order
8
- # @deprecated
9
- LEGACY_SIGNATURE_ORDER = %i(guid parent_type parent_guid author).freeze
7
+ # @!attribute [r] author
8
+ # The diaspora* ID of the author
9
+ # @see Person#author
10
+ # @return [String] diaspora* ID
11
+ property :author, :string, xml_name: :diaspora_handle
10
12
 
11
- include Relayable
13
+ # @!attribute [r] guid
14
+ # A random string of at least 16 chars
15
+ # @see Validation::Rule::Guid
16
+ # @return [String] guid
17
+ property :guid, :string
18
+
19
+ # @!attribute [r] parent_guid
20
+ # @see StatusMessage#guid
21
+ # @return [String] parent guid
22
+ property :parent_guid, :string
12
23
 
13
24
  # @!attribute [r] parent_type
14
25
  # A string describing a type of the target to subscribe on
@@ -16,10 +27,9 @@ module DiasporaFederation
16
27
  # @return [String] parent type
17
28
  property :parent_type, :string, xml_name: :target_type
18
29
 
19
- # It is only valid to receive a {Participation} from the author themself.
20
- # @deprecated remove after {Participation} doesn't include {Relayable} anymore
21
- def sender_valid?(sender)
22
- sender == author
30
+ # @return [String] string representation of this object
31
+ def to_s
32
+ "#{super}:#{parent_type}:#{parent_guid}"
23
33
  end
24
34
 
25
35
  # Validates that the parent exists and the parent author is local
@@ -28,12 +38,12 @@ module DiasporaFederation
28
38
  raise ParentNotLocal, "obj=#{self}" unless parent && parent.local
29
39
  end
30
40
 
31
- # Don't verify signatures for a {Participation}. Validate that the parent is local.
32
- # @see Entity.populate_entity
33
- # @param [Nokogiri::XML::Element] root_node xml nodes
41
+ # Validate that the parent is local.
42
+ # @see Entity.from_hash
43
+ # @param [Hash] hash entity initialization hash
34
44
  # @return [Entity] instance
35
- private_class_method def self.populate_entity(root_node)
36
- new(entity_data(root_node).merge(parent: nil)).tap(&:validate_parent)
45
+ def self.from_hash(hash)
46
+ super.tap(&:validate_parent)
37
47
  end
38
48
 
39
49
  # Raised, if the parent is not owned by the receiving pod.
@@ -4,10 +4,6 @@ module DiasporaFederation
4
4
  #
5
5
  # @see Validators::PollParticipationValidator
6
6
  class PollParticipation < Entity
7
- # Old signature order
8
- # @deprecated
9
- LEGACY_SIGNATURE_ORDER = %i(guid parent_guid author poll_answer_guid).freeze
10
-
11
7
  # The {PollParticipation} parent is a {Poll}
12
8
  PARENT_TYPE = "Poll".freeze
13
9
 
@@ -53,6 +53,11 @@ module DiasporaFederation
53
53
  # @return [Boolean] searchable flag
54
54
  property :searchable, :boolean, default: true
55
55
 
56
+ # @!attribute [r] public
57
+ # Shows whether the profile is visible to everyone or only to contacts
58
+ # @return [Boolean] is it public
59
+ property :public, :boolean, default: false
60
+
56
61
  property :nsfw, :boolean, default: false
57
62
  property :tag_string, :string, default: nil
58
63
 
@@ -24,10 +24,27 @@ module DiasporaFederation
24
24
  # @return [RelatedEntity] parent entity
25
25
  entity :parent, Entities::RelatedEntity, default: nil
26
26
 
27
+ # Get related entity from the backend or fetch it from remote if not available locally
28
+ # @return [RelatedEntity] fetched related entity
29
+ def self.fetch(author, type, guid)
30
+ # Try to fetch locally
31
+ entity = DiasporaFederation.callbacks.trigger(:fetch_related_entity, type, guid)
32
+ return entity if entity
33
+
34
+ # Fetch and receive entity from remote if not available locally
35
+ Federation::Fetcher.fetch_public(author, type, guid)
36
+ DiasporaFederation.callbacks.trigger(:fetch_related_entity, type, guid)
37
+ end
38
+
27
39
  # never add {RelatedEntity} to xml
28
40
  def to_xml
29
41
  nil
30
42
  end
43
+
44
+ # never add {RelatedEntity} to json
45
+ def to_json
46
+ nil
47
+ end
31
48
  end
32
49
  end
33
50
  end
@@ -5,18 +5,11 @@ module DiasporaFederation
5
5
  # has a parent, identified by guid. Relayables are also signed and signing/verification
6
6
  # logic is embedded into Salmon XML processing code.
7
7
  module Relayable
8
- include Logging
8
+ include Signable
9
9
 
10
- # Digest instance used for signing
11
- DIGEST = OpenSSL::Digest::SHA256.new
12
-
13
- # Order from the parsed xml for signature
14
- # @return [Array] order from xml
15
- attr_reader :xml_order
16
-
17
- # Additional properties from parsed xml
18
- # @return [Hash] additional xml elements
19
- attr_reader :additional_xml_elements
10
+ # Additional properties from parsed input object
11
+ # @return [Hash] additional elements
12
+ attr_reader :additional_data
20
13
 
21
14
  # On inclusion of this module the required properties for a relayable are added to the object that includes it.
22
15
  #
@@ -35,16 +28,15 @@ module DiasporaFederation
35
28
  # @return [String] parent guid
36
29
  #
37
30
  # @!attribute [r] author_signature
38
- # Contains a signature of the entity using the private key of the author of a post itself
31
+ # Contains a signature of the entity using the private key of the author of a relayable itself.
39
32
  # The presence of this signature is mandatory. Without it the entity won't be accepted by
40
33
  # a target pod.
41
34
  # @return [String] author signature
42
35
  #
43
36
  # @!attribute [r] parent_author_signature
44
- # Contains a signature of the entity using the private key of the author of a parent post
45
- # This signature is required only when federating from upstream (parent) post author to
46
- # downstream subscribers. This is the case when the parent author has to resend a relayable
47
- # received from one of their subscribers to all others.
37
+ # Contains a signature of the entity using the private key of the author of a parent post.
38
+ # @deprecated This signature isn't required anymore, because we can check the signature from
39
+ # the parent author in the MagicEnvelope.
48
40
  # @return [String] parent author signature
49
41
  #
50
42
  # @!attribute [r] parent
@@ -62,33 +54,33 @@ module DiasporaFederation
62
54
  entity :parent, Entities::RelatedEntity
63
55
  end
64
56
 
65
- klass.extend ParseXML
57
+ klass.extend Parsing
66
58
  end
67
59
 
68
60
  # Initializes a new relayable Entity with order and additional xml elements
69
61
  #
70
62
  # @param [Hash] data entity data
71
- # @param [Array] xml_order order from xml
72
- # @param [Hash] additional_xml_elements additional xml elements
63
+ # @param [Array] signature_order order for the signature
64
+ # @param [Hash] additional_data additional xml elements
73
65
  # @see DiasporaFederation::Entity#initialize
74
- def initialize(data, xml_order=nil, additional_xml_elements={})
75
- @xml_order = xml_order.reject {|name| name =~ /signature/ } if xml_order
76
- @additional_xml_elements = additional_xml_elements
66
+ def initialize(data, signature_order=nil, additional_data={})
67
+ self.signature_order = signature_order if signature_order
68
+ @additional_data = additional_data
77
69
 
78
70
  super(data)
79
71
  end
80
72
 
81
- # Verifies the signatures (+author_signature+ and +parent_author_signature+ if needed).
82
- # @raise [SignatureVerificationFailed] if the signature is not valid or no public key is found
83
- def verify_signatures
84
- verify_signature(author, :author_signature)
85
-
86
- # This happens only on downstream federation.
87
- verify_signature(parent.author, :parent_author_signature) unless parent.local
73
+ # Verifies the +author_signature+ if needed.
74
+ # @see DiasporaFederation::Entities::Signable#verify_signature
75
+ #
76
+ # @raise [SignatureVerificationFailed] if the signature is not valid
77
+ # @raise [PublicKeyNotFound] if no public key is found
78
+ def verify_signature
79
+ super(author, :author_signature) unless author == parent.author
88
80
  end
89
81
 
90
82
  def sender_valid?(sender)
91
- sender == author || sender == parent.author
83
+ (sender == author && parent.local) || sender == parent.author
92
84
  end
93
85
 
94
86
  # @return [String] string representation of this object
@@ -96,26 +88,23 @@ module DiasporaFederation
96
88
  "#{super}#{":#{parent_type}" if respond_to?(:parent_type)}:#{parent_guid}"
97
89
  end
98
90
 
99
- private
100
-
101
- # Check that signature is a correct signature
102
- #
103
- # @param [String] author The author of the signature
104
- # @param [String] signature_key The signature to be verified
105
- # @return [Boolean] signature valid
106
- def verify_signature(author, signature_key)
107
- pubkey = DiasporaFederation.callbacks.trigger(:fetch_public_key, author)
108
- raise PublicKeyNotFound, "signature=#{signature_key} person=#{author} obj=#{self}" if pubkey.nil?
109
-
110
- signature = public_send(signature_key)
111
- raise SignatureVerificationFailed, "no #{signature_key} for #{self}" if signature.nil?
112
-
113
- valid = pubkey.verify(DIGEST, Base64.decode64(signature), signature_data)
114
- raise SignatureVerificationFailed, "wrong #{signature_key} for #{self}" unless valid
91
+ def to_json
92
+ super.merge!(property_order: signature_order).tap {|json_hash|
93
+ missing_properties = json_hash[:property_order] - json_hash[:entity_data].keys
94
+ missing_properties.each {|property|
95
+ json_hash[:entity_data][property] = nil
96
+ }
97
+ }
98
+ end
115
99
 
116
- logger.info "event=verify_signature signature=#{signature_key} status=valid obj=#{self}"
100
+ # The order for signing
101
+ # @return [Array]
102
+ def signature_order
103
+ @signature_order || self.class.class_props.keys - %i[author_signature parent_author_signature parent]
117
104
  end
118
105
 
106
+ private
107
+
119
108
  # Sign with author key
120
109
  # @raise [AuthorPrivateKeyNotFound] if the author private key is not found
121
110
  # @return [String] A Base64 encoded signature of #signature_data with key
@@ -138,22 +127,14 @@ module DiasporaFederation
138
127
  end
139
128
  end
140
129
 
141
- # Sign the data with the key
142
- #
143
- # @param [OpenSSL::PKey::RSA] privkey An RSA key
144
- # @return [String] A Base64 encoded signature of #signature_data with key
145
- def sign_with_key(privkey)
146
- Base64.strict_encode64(privkey.sign(DIGEST, signature_data))
147
- end
148
-
149
130
  # Update the signatures with the keys of the author and the parent
150
131
  # if the signatures are not there yet and if the keys are available.
151
132
  #
152
133
  # @return [Hash] properties with updated signatures
153
134
  def enriched_properties
154
- super.merge(additional_xml_elements).tap do |hash|
135
+ super.merge(additional_data).tap do |hash|
155
136
  hash[:author_signature] = author_signature || sign_with_author
156
- hash[:parent_author_signature] = parent_author_signature || sign_with_parent_author_if_available.to_s
137
+ hash.delete(:parent_author_signature)
157
138
  end
158
139
  end
159
140
 
@@ -161,90 +142,78 @@ module DiasporaFederation
161
142
  #
162
143
  # @return [Hash] sorted xml elements
163
144
  def xml_elements
164
- data = super
165
- order = signature_order + %i(author_signature parent_author_signature)
145
+ data = super.tap do |hash|
146
+ hash[:parent_author_signature] = parent_author_signature || sign_with_parent_author_if_available.to_s
147
+ end
148
+ order = signature_order + %i[author_signature parent_author_signature]
166
149
  order.map {|element| [element, data[element] || ""] }.to_h
167
150
  end
168
151
 
169
- # The order for signing
170
- # @return [Array]
171
- def signature_order
172
- if xml_order
173
- prop_names = self.class.class_props.keys.map(&:to_s)
174
- xml_order.map {|name| prop_names.include?(name) ? name.to_sym : name }
175
- else
176
- self.class::LEGACY_SIGNATURE_ORDER
177
- end
152
+ def signature_order=(order)
153
+ prop_names = self.class.class_props.keys.map(&:to_s)
154
+ @signature_order = order.reject {|name| name =~ /signature/ }
155
+ .map {|name| prop_names.include?(name) ? name.to_sym : name }
178
156
  end
179
157
 
180
158
  # @return [String] signature data string
181
159
  def signature_data
182
- data = normalized_properties.merge(additional_xml_elements)
160
+ data = normalized_properties.merge(additional_data)
183
161
  signature_order.map {|name| data[name] }.join(";")
184
162
  end
185
163
 
186
- # Override class methods from {Entity} to parse the xml
187
- module ParseXML
188
- private
189
-
190
- # @param [Nokogiri::XML::Element] root_node xml nodes
191
- # @return [Entity] instance
192
- def populate_entity(root_node)
193
- # Use all known properties to build the Entity (entity_data). All additional xml elements
194
- # are respected and attached to a hash as string (additional_xml_elements). It also remembers
195
- # the order of the xml-nodes (xml_order). This is needed to support receiving objects from
196
- # the future versions of diaspora*, where new elements may have been added.
197
- entity_data = {}
198
- additional_xml_elements = {}
199
-
200
- xml_order = root_node.element_children.map do |child|
201
- xml_name = child.name
202
- property = find_property_for_xml_name(xml_name)
203
-
204
- if property
205
- entity_data[property] = parse_element_from_node(xml_name, class_props[property], root_node)
206
- property
207
- else
208
- additional_xml_elements[xml_name] = child.text
209
- xml_name
210
- end
211
- end
212
-
213
- fetch_parent(entity_data)
214
- new(entity_data, xml_order, additional_xml_elements).tap(&:verify_signatures)
164
+ # Override class methods from {Entity} to parse serialized data
165
+ module Parsing
166
+ # Does the same job as Entity.from_hash except of the following differences:
167
+ # 1) unknown properties from the properties_hash are stored to additional_data of the relayable instance
168
+ # 2) parent entity fetch is attempted
169
+ # 3) signatures verification is performed; property_order is used as the order in which properties are composed
170
+ # to compute signatures
171
+ # 4) unknown properties' keys must be of String type
172
+ #
173
+ # @see Entity.from_hash
174
+ def from_hash(properties_hash, property_order)
175
+ # Use all known properties to build the Entity (entity_data). All additional elements
176
+ # are respected and attached to a hash as string (additional_data). This is needed
177
+ # to support receiving objects from the future versions of diaspora*, where new elements may have been added.
178
+ additional_data = properties_hash.reject {|key, _| class_props.has_key?(key) }
179
+
180
+ fetch_parent(properties_hash)
181
+ new(properties_hash, property_order, additional_data).tap(&:verify_signature)
215
182
  end
216
183
 
184
+ private
185
+
217
186
  def fetch_parent(data)
218
187
  type = data.fetch(:parent_type) {
219
188
  break self::PARENT_TYPE if const_defined?(:PARENT_TYPE)
220
189
 
221
- raise DiasporaFederation::Entity::ValidationError, "invalid #{self}! missing 'parent_type'."
190
+ raise DiasporaFederation::Entity::ValidationError, error_message_missing_property(data, "parent_type")
222
191
  }
223
192
  guid = data.fetch(:parent_guid) {
224
- raise DiasporaFederation::Entity::ValidationError, "invalid #{self}! missing 'parent_guid'."
193
+ raise DiasporaFederation::Entity::ValidationError, error_message_missing_property(data, "parent_guid")
225
194
  }
226
195
 
227
- data[:parent] = DiasporaFederation.callbacks.trigger(:fetch_related_entity, type, guid)
196
+ data[:parent] = RelatedEntity.fetch(data[:author], type, guid)
197
+ end
228
198
 
229
- return if data[:parent]
199
+ def error_message_missing_property(data, missing_property)
200
+ obj_str = "#{class_name}#{":#{data[:guid]}" if data.has_key?(:guid)}" \
201
+ "#{" from #{data[:author]}" if data.has_key?(:author)}"
202
+ "Invalid #{obj_str}! Missing '#{missing_property}'."
203
+ end
204
+
205
+ def xml_parser_class
206
+ DiasporaFederation::Parsers::RelayableXmlParser
207
+ end
230
208
 
231
- # Fetch and receive parent from remote, if not available locally
232
- Federation::Fetcher.fetch_public(data[:author], type, guid)
233
- data[:parent] = DiasporaFederation.callbacks.trigger(:fetch_related_entity, type, guid)
209
+ def json_parser_class
210
+ DiasporaFederation::Parsers::RelayableJsonParser
234
211
  end
235
212
  end
236
213
 
237
214
  # Raised, if creating the author_signature fails, because the private key was not found
238
215
  class AuthorPrivateKeyNotFound < RuntimeError
239
216
  end
240
-
241
- # Raised, if verify_signatures fails to verify signatures (no public key found)
242
- class PublicKeyNotFound < RuntimeError
243
- end
244
-
245
- # Raised, if verify_signatures fails to verify signatures (signatures are wrong)
246
- class SignatureVerificationFailed < RuntimeError
247
- end
248
217
  end
249
218
  end
250
219
  end
@@ -53,52 +53,13 @@ module DiasporaFederation
53
53
  # @return [String] target author signature
54
54
  property :target_author_signature, :string, default: nil
55
55
 
56
- # @!attribute [r] target
57
- # Target entity
58
- # @return [RelatedEntity] target entity
59
- entity :target, Entities::RelatedEntity
60
-
61
- # Use only {Retraction} for receive
62
- # @return [Retraction] instance as normal retraction
63
- def to_retraction
64
- Retraction.new(author: author, target_guid: target_guid, target_type: target_type, target: target)
65
- end
66
-
67
- # @return [String] string representation of this object
68
- def to_s
69
- "RelayableRetraction:#{target_type}:#{target_guid}"
56
+ def initialize(*)
57
+ raise "Sending RelayableRetraction is not supported anymore! Use Retraction instead!"
70
58
  end
71
59
 
72
- private
73
-
74
- # @param [Nokogiri::XML::Element] root_node xml nodes
75
60
  # @return [Retraction] instance
76
- private_class_method def self.populate_entity(root_node)
77
- entity_data = entity_data(root_node)
78
- entity_data[:target] = Retraction.send(:fetch_target, entity_data[:target_type], entity_data[:target_guid])
79
- new(entity_data).to_retraction
80
- end
81
-
82
- # It updates also the signatures with the keys of the author and the parent
83
- # if the signatures are not there yet and if the keys are available.
84
- #
85
- # @return [Hash] xml elements with updated signatures
86
- def enriched_properties
87
- privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key, author)
88
-
89
- super.tap do |hash|
90
- fill_required_signature(privkey, hash) unless privkey.nil?
91
- end
92
- end
93
-
94
- # @param [OpenSSL::PKey::RSA] privkey private key of sender
95
- # @param [Hash] hash hash given for a signing
96
- def fill_required_signature(privkey, hash)
97
- if target.parent.author == author && parent_author_signature.nil?
98
- hash[:parent_author_signature] = SignedRetraction.sign_with_key(privkey, self)
99
- elsif target.author == author && target_author_signature.nil?
100
- hash[:target_author_signature] = SignedRetraction.sign_with_key(privkey, self)
101
- end
61
+ def self.from_hash(hash)
62
+ Retraction.from_hash(hash)
102
63
  end
103
64
  end
104
65
  end
@@ -18,21 +18,13 @@ module DiasporaFederation
18
18
  # @return [String] recipient ID
19
19
  property :recipient, :string, xml_name: :recipient_handle
20
20
 
21
- # Use only {Contact} for receive
22
- # @return [Contact] instance as contact
23
- def to_contact
24
- Contact.new(author: author, recipient: recipient)
21
+ def initialize(*)
22
+ raise "Sending Request is not supported anymore! Use Contact instead!"
25
23
  end
26
24
 
27
- # @return [String] string representation of this object
28
- def to_s
29
- "Request:#{author}:#{recipient}"
30
- end
31
-
32
- # @param [Nokogiri::XML::Element] root_node xml nodes
33
25
  # @return [Retraction] instance
34
- private_class_method def self.populate_entity(root_node)
35
- super(root_node).to_contact
26
+ def self.from_hash(hash)
27
+ Contact.new(hash)
36
28
  end
37
29
  end
38
30
  end
@@ -29,17 +29,21 @@ module DiasporaFederation
29
29
  end
30
30
 
31
31
  # Fetch and receive root post from remote, if not available locally
32
- def fetch_root
33
- root = DiasporaFederation.callbacks.trigger(:fetch_related_entity, "Post", root_guid)
34
- Federation::Fetcher.fetch_public(root_author, "Post", root_guid) unless root
32
+ # and validates if it's from the correct author
33
+ def validate_root
34
+ root = RelatedEntity.fetch(root_author, "Post", root_guid)
35
+
36
+ return if root_author == root.author
37
+
38
+ raise Entity::ValidationError,
39
+ "root_author mismatch: obj=#{self} root_author=#{root_author} known_root_author=#{root.author}"
35
40
  end
36
41
 
37
42
  # Fetch root post after parse
38
- # @see Entity.populate_entity
39
- # @param [Nokogiri::XML::Element] root_node xml nodes
43
+ # @see Entity.from_hash
40
44
  # @return [Entity] instance
41
- private_class_method def self.populate_entity(root_node)
42
- super(root_node).tap(&:fetch_root)
45
+ def self.from_hash(hash)
46
+ super.tap(&:validate_root)
43
47
  end
44
48
  end
45
49
  end
@@ -39,12 +39,11 @@ module DiasporaFederation
39
39
  "Retraction:#{target_type}:#{target_guid}"
40
40
  end
41
41
 
42
- # @param [Nokogiri::XML::Element] root_node xml nodes
42
+ # @see Entity.from_hash
43
43
  # @return [Retraction] instance
44
- private_class_method def self.populate_entity(root_node)
45
- entity_data = entity_data(root_node)
46
- entity_data[:target] = fetch_target(entity_data[:target_type], entity_data[:target_guid])
47
- new(entity_data)
44
+ def self.from_hash(hash)
45
+ hash[:target] = fetch_target(hash[:target_type], hash[:target_guid])
46
+ new(hash)
48
47
  end
49
48
 
50
49
  private_class_method def self.fetch_target(target_type, target_guid)
@@ -0,0 +1,54 @@
1
+ module DiasporaFederation
2
+ module Entities
3
+ # Signable is a module that encapsulates basic signature generation/verification flow for entities.
4
+ module Signable
5
+ include Logging
6
+
7
+ # Digest instance used for signing
8
+ DIGEST = OpenSSL::Digest::SHA256.new
9
+
10
+ # Sign the data with the key
11
+ #
12
+ # @param [OpenSSL::PKey::RSA] privkey An RSA key
13
+ # @return [String] A Base64 encoded signature of #signature_data with key
14
+ def sign_with_key(privkey)
15
+ Base64.strict_encode64(privkey.sign(DIGEST, signature_data))
16
+ end
17
+
18
+ # Check that signature is a correct signature
19
+ #
20
+ # @param [String] author The author of the signature
21
+ # @param [String] signature_key The signature to be verified
22
+ # @raise [SignatureVerificationFailed] if the signature is not valid
23
+ # @raise [PublicKeyNotFound] if no public key is found
24
+ def verify_signature(author, signature_key)
25
+ pubkey = DiasporaFederation.callbacks.trigger(:fetch_public_key, author)
26
+ raise PublicKeyNotFound, "signature=#{signature_key} person=#{author} obj=#{self}" if pubkey.nil?
27
+
28
+ signature = public_send(signature_key)
29
+ raise SignatureVerificationFailed, "no #{signature_key} for #{self}" if signature.nil?
30
+
31
+ valid = pubkey.verify(DIGEST, Base64.decode64(signature), signature_data)
32
+ raise SignatureVerificationFailed, "wrong #{signature_key} for #{self}" unless valid
33
+
34
+ logger.info "event=verify_signature signature=#{signature_key} status=valid obj=#{self}"
35
+ end
36
+
37
+ # This method defines what data is used for a signature creation/verification
38
+ #
39
+ # @abstract
40
+ # @return [String] a string to sign
41
+ def signature_data
42
+ raise NotImplementedError.new("you must override this method to define signature base string")
43
+ end
44
+
45
+ # Raised, if verify_signatures fails to verify signatures (no public key found)
46
+ class PublicKeyNotFound < RuntimeError
47
+ end
48
+
49
+ # Raised, if verify_signatures fails to verify signatures (signatures are wrong)
50
+ class SignatureVerificationFailed < RuntimeError
51
+ end
52
+ end
53
+ end
54
+ end
@@ -30,53 +30,13 @@ module DiasporaFederation
30
30
  # @return [String] author signature
31
31
  property :target_author_signature, :string, default: nil
32
32
 
33
- # @!attribute [r] target
34
- # Target entity
35
- # @return [RelatedEntity] target entity
36
- entity :target, Entities::RelatedEntity
37
-
38
- # Use only {Retraction} for receive
39
- # @return [Retraction] instance as normal retraction
40
- def to_retraction
41
- Retraction.new(author: author, target_guid: target_guid, target_type: target_type, target: target)
42
- end
43
-
44
- # Create signature for a retraction
45
- # @param [OpenSSL::PKey::RSA] privkey private key of sender
46
- # @param [SignedRetraction, RelayableRetraction] ret the retraction to sign
47
- # @return [String] a Base64 encoded signature of the retraction with the key
48
- def self.sign_with_key(privkey, ret)
49
- Base64.strict_encode64(privkey.sign(Relayable::DIGEST, [ret.target_guid, ret.target_type].join(";")))
50
- end
51
-
52
- # @return [String] string representation of this object
53
- def to_s
54
- "SignedRetraction:#{target_type}:#{target_guid}"
33
+ def initialize(*)
34
+ raise "Sending SignedRetraction is not supported anymore! Use Retraction instead!"
55
35
  end
56
36
 
57
- private
58
-
59
- # @param [Nokogiri::XML::Element] root_node xml nodes
60
37
  # @return [Retraction] instance
61
- private_class_method def self.populate_entity(root_node)
62
- entity_data = entity_data(root_node)
63
- entity_data[:target] = Retraction.send(:fetch_target, entity_data[:target_type], entity_data[:target_guid])
64
- new(entity_data).to_retraction
65
- end
66
-
67
- # It updates also the signatures with the keys of the author and the parent
68
- # if the signatures are not there yet and if the keys are available.
69
- #
70
- # @return [Hash] xml elements with updated signatures
71
- def enriched_properties
72
- super.tap do |hash|
73
- hash[:target_author_signature] = target_author_signature || sign_with_author.to_s
74
- end
75
- end
76
-
77
- def sign_with_author
78
- privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key, author)
79
- SignedRetraction.sign_with_key(privkey, self) unless privkey.nil?
38
+ def self.from_hash(hash)
39
+ Retraction.from_hash(hash)
80
40
  end
81
41
  end
82
42
  end