diaspora_federation 0.1.9 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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