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.
- checksums.yaml +4 -4
- data/Changelog.md +34 -0
- data/lib/diaspora_federation/discovery/discovery.rb +2 -2
- data/lib/diaspora_federation/discovery/h_card.rb +2 -10
- data/lib/diaspora_federation/discovery/host_meta.rb +1 -1
- data/lib/diaspora_federation/discovery/web_finger.rb +47 -87
- data/lib/diaspora_federation/discovery/xrd_document.rb +20 -7
- data/lib/diaspora_federation/entities/account_migration.rb +74 -0
- data/lib/diaspora_federation/entities/comment.rb +0 -4
- data/lib/diaspora_federation/entities/event_participation.rb +0 -8
- data/lib/diaspora_federation/entities/like.rb +6 -10
- data/lib/diaspora_federation/entities/message.rb +14 -49
- data/lib/diaspora_federation/entities/participation.rb +23 -13
- data/lib/diaspora_federation/entities/poll_participation.rb +0 -4
- data/lib/diaspora_federation/entities/profile.rb +5 -0
- data/lib/diaspora_federation/entities/related_entity.rb +17 -0
- data/lib/diaspora_federation/entities/relayable.rb +82 -113
- data/lib/diaspora_federation/entities/relayable_retraction.rb +4 -43
- data/lib/diaspora_federation/entities/request.rb +4 -12
- data/lib/diaspora_federation/entities/reshare.rb +11 -7
- data/lib/diaspora_federation/entities/retraction.rb +4 -5
- data/lib/diaspora_federation/entities/signable.rb +54 -0
- data/lib/diaspora_federation/entities/signed_retraction.rb +4 -44
- data/lib/diaspora_federation/entities.rb +2 -0
- data/lib/diaspora_federation/entity.rb +74 -96
- data/lib/diaspora_federation/federation/fetcher.rb +1 -1
- data/lib/diaspora_federation/federation/receiver.rb +1 -1
- data/lib/diaspora_federation/federation/sender/hydra_wrapper.rb +37 -12
- data/lib/diaspora_federation/federation/sender.rb +2 -2
- data/lib/diaspora_federation/parsers/base_parser.rb +61 -0
- data/lib/diaspora_federation/parsers/json_parser.rb +60 -0
- data/lib/diaspora_federation/parsers/relayable_json_parser.rb +25 -0
- data/lib/diaspora_federation/parsers/relayable_xml_parser.rb +22 -0
- data/lib/diaspora_federation/parsers/xml_parser.rb +84 -0
- data/lib/diaspora_federation/parsers.rb +13 -0
- data/lib/diaspora_federation/properties_dsl.rb +1 -1
- data/lib/diaspora_federation/salmon/encrypted_magic_envelope.rb +1 -1
- data/lib/diaspora_federation/salmon/encrypted_slap.rb +2 -99
- data/lib/diaspora_federation/salmon/magic_envelope.rb +3 -19
- data/lib/diaspora_federation/salmon/slap.rb +1 -42
- data/lib/diaspora_federation/salmon/xml_payload.rb +0 -19
- data/lib/diaspora_federation/schemas/federation_entities.json +379 -0
- data/lib/diaspora_federation/validators/account_deletion_validator.rb +1 -1
- data/lib/diaspora_federation/validators/account_migration_validator.rb +12 -0
- data/lib/diaspora_federation/validators/contact_validator.rb +2 -2
- data/lib/diaspora_federation/validators/conversation_validator.rb +1 -1
- data/lib/diaspora_federation/validators/event_validator.rb +1 -1
- data/lib/diaspora_federation/validators/h_card_validator.rb +2 -5
- data/lib/diaspora_federation/validators/message_validator.rb +1 -1
- data/lib/diaspora_federation/validators/participation_validator.rb +1 -1
- data/lib/diaspora_federation/validators/person_validator.rb +2 -2
- data/lib/diaspora_federation/validators/photo_validator.rb +1 -1
- data/lib/diaspora_federation/validators/profile_validator.rb +1 -1
- data/lib/diaspora_federation/validators/related_entity_validator.rb +1 -1
- data/lib/diaspora_federation/validators/relayable_validator.rb +1 -1
- data/lib/diaspora_federation/validators/reshare_validator.rb +2 -2
- data/lib/diaspora_federation/validators/retraction_validator.rb +1 -1
- data/lib/diaspora_federation/validators/rules/boolean.rb +2 -2
- data/lib/diaspora_federation/validators/status_message_validator.rb +1 -1
- data/lib/diaspora_federation/validators/web_finger_validator.rb +5 -6
- data/lib/diaspora_federation/validators.rb +1 -5
- data/lib/diaspora_federation/version.rb +1 -1
- data/lib/diaspora_federation.rb +6 -2
- metadata +14 -11
- data/lib/diaspora_federation/validators/relayable_retraction_validator.rb +0 -15
- data/lib/diaspora_federation/validators/request_validator.rb +0 -12
- data/lib/diaspora_federation/validators/signed_retraction_validator.rb +0 -15
- data/lib/tasks/build.rake +0 -14
- data/lib/tasks/diaspora_federation_tasks.rake +0 -4
- data/lib/tasks/rails4.rake +0 -15
- data/lib/tasks/tests.rake +0 -18
@@ -12,6 +12,7 @@ require "diaspora_federation/entities/related_entity"
|
|
12
12
|
|
13
13
|
# abstract types
|
14
14
|
require "diaspora_federation/entities/post"
|
15
|
+
require "diaspora_federation/entities/signable"
|
15
16
|
require "diaspora_federation/entities/relayable"
|
16
17
|
|
17
18
|
# types
|
@@ -19,6 +20,7 @@ require "diaspora_federation/entities/profile"
|
|
19
20
|
require "diaspora_federation/entities/person"
|
20
21
|
require "diaspora_federation/entities/contact"
|
21
22
|
require "diaspora_federation/entities/account_deletion"
|
23
|
+
require "diaspora_federation/entities/account_migration"
|
22
24
|
|
23
25
|
require "diaspora_federation/entities/participation"
|
24
26
|
require "diaspora_federation/entities/like"
|
@@ -90,7 +90,6 @@ module DiasporaFederation
|
|
90
90
|
# {http://www.rubydoc.info/gems/nokogiri/Nokogiri/XML/Element Nokogiri::XML::Element}s
|
91
91
|
#
|
92
92
|
# @see Nokogiri::XML::Node.to_xml
|
93
|
-
# @see XmlPayload#pack
|
94
93
|
#
|
95
94
|
# @return [Nokogiri::XML::Element] root element containing properties as child elements
|
96
95
|
def to_xml
|
@@ -109,10 +108,21 @@ module DiasporaFederation
|
|
109
108
|
# @param [Nokogiri::XML::Element] root_node xml nodes
|
110
109
|
# @return [Entity] instance
|
111
110
|
def self.from_xml(root_node)
|
112
|
-
|
113
|
-
|
111
|
+
from_hash(*xml_parser_class.new(self).parse(root_node))
|
112
|
+
end
|
113
|
+
|
114
|
+
private_class_method def self.xml_parser_class
|
115
|
+
DiasporaFederation::Parsers::XmlParser
|
116
|
+
end
|
114
117
|
|
115
|
-
|
118
|
+
# Creates an instance of self by parsing a hash in the format of JSON serialized object (which usually means
|
119
|
+
# data from a parsed JSON input).
|
120
|
+
def self.from_json(json_hash)
|
121
|
+
from_hash(*json_parser_class.new(self).parse(json_hash))
|
122
|
+
end
|
123
|
+
|
124
|
+
private_class_method def self.json_parser_class
|
125
|
+
DiasporaFederation::Parsers::JsonParser
|
116
126
|
end
|
117
127
|
|
118
128
|
# Makes an underscored, lowercase form of the class name
|
@@ -121,7 +131,7 @@ module DiasporaFederation
|
|
121
131
|
#
|
122
132
|
# @return [String] entity name
|
123
133
|
def self.entity_name
|
124
|
-
|
134
|
+
class_name.tap do |word|
|
125
135
|
word.gsub!(/(.)([A-Z])/, '\1_\2')
|
126
136
|
word.downcase!
|
127
137
|
end
|
@@ -144,16 +154,52 @@ module DiasporaFederation
|
|
144
154
|
Entities.const_get(class_name)
|
145
155
|
end
|
146
156
|
|
157
|
+
# @return [String] class name as string
|
158
|
+
def self.class_name
|
159
|
+
name.rpartition("::").last
|
160
|
+
end
|
161
|
+
|
147
162
|
# @return [String] string representation of this object
|
148
163
|
def to_s
|
149
|
-
"#{self.class.
|
164
|
+
"#{self.class.class_name}#{":#{guid}" if respond_to?(:guid)}"
|
165
|
+
end
|
166
|
+
|
167
|
+
# Renders entity to a hash representation of the entity JSON format
|
168
|
+
# @return [Hash] Returns a hash that is equal by structure to the entity in JSON format
|
169
|
+
def to_json
|
170
|
+
{
|
171
|
+
entity_type: self.class.entity_name,
|
172
|
+
entity_data: json_data
|
173
|
+
}
|
174
|
+
end
|
175
|
+
|
176
|
+
# Creates an instance of self, filling it with data from a provided hash of properties.
|
177
|
+
#
|
178
|
+
# The hash format is described as following:<br>
|
179
|
+
# 1) Properties of the hash are representation of the entity's class properties<br>
|
180
|
+
# 2) Keys of the hash must be of Symbol type<br>
|
181
|
+
# 3) Possible values of the hash properties depend on the types of the entity's class properties<br>
|
182
|
+
# 4) Basic properties, such as booleans, strings, integers and timestamps are represented by values of respective
|
183
|
+
# formats<br>
|
184
|
+
# 5) Nested hashes and arrays of hashes are allowed to represent nested entities. Nested hashes follow the same
|
185
|
+
# format as the parent hash.<br>
|
186
|
+
# 6) Besides, the nested entities can be passed in the hash as already instantiated objects of the respective type.
|
187
|
+
#
|
188
|
+
# @param [Hash] properties_hash A hash of the expected format
|
189
|
+
# @return [Entity] an instance
|
190
|
+
def self.from_hash(properties_hash)
|
191
|
+
new(properties_hash)
|
150
192
|
end
|
151
193
|
|
152
194
|
private
|
153
195
|
|
154
196
|
def validate_missing_props(entity_data)
|
155
197
|
missing_props = self.class.missing_props(entity_data)
|
156
|
-
|
198
|
+
return if missing_props.empty?
|
199
|
+
|
200
|
+
obj_str = "#{self.class.class_name}#{":#{entity_data[:guid]}" if entity_data.has_key?(:guid)}" \
|
201
|
+
"#{" from #{entity_data[:author]}" if entity_data.has_key?(:author)}"
|
202
|
+
raise ValidationError, "#{obj_str}: Missing required properties: #{missing_props.join(', ')}"
|
157
203
|
end
|
158
204
|
|
159
205
|
def setable?(name, val)
|
@@ -168,7 +214,7 @@ module DiasporaFederation
|
|
168
214
|
end
|
169
215
|
|
170
216
|
def setable_string?(type, val)
|
171
|
-
%i
|
217
|
+
%i[string integer boolean].include?(type) && val.respond_to?(:to_s)
|
172
218
|
end
|
173
219
|
|
174
220
|
def setable_nested?(type, val)
|
@@ -209,7 +255,7 @@ module DiasporaFederation
|
|
209
255
|
errors = validator.errors.map do |prop, rule|
|
210
256
|
"property: #{prop}, value: #{public_send(prop).inspect}, rule: #{rule[:rule]}, with params: #{rule[:params]}"
|
211
257
|
end
|
212
|
-
"Failed validation for properties: #{errors.join(' | ')}"
|
258
|
+
"Failed validation for #{self}#{" from #{author}" if respond_to?(:author)} for properties: #{errors.join(' | ')}"
|
213
259
|
end
|
214
260
|
|
215
261
|
# @return [Hash] hash with all properties
|
@@ -225,7 +271,7 @@ module DiasporaFederation
|
|
225
271
|
|
226
272
|
def normalize_property(name, value)
|
227
273
|
case self.class.class_props[name]
|
228
|
-
when :string
|
274
|
+
when :string
|
229
275
|
value.to_s
|
230
276
|
when :timestamp
|
231
277
|
value.nil? ? "" : value.utc.iso8601
|
@@ -245,8 +291,8 @@ module DiasporaFederation
|
|
245
291
|
end
|
246
292
|
|
247
293
|
def add_property_to_xml(doc, root_element, name, value)
|
248
|
-
if value.is_a?
|
249
|
-
root_element << simple_node(doc, name, value)
|
294
|
+
if [String, TrueClass, FalseClass, Integer].any? {|c| value.is_a? c }
|
295
|
+
root_element << simple_node(doc, name, value.to_s)
|
250
296
|
else
|
251
297
|
# call #to_xml for each item and append to root
|
252
298
|
[*value].compact.each do |item|
|
@@ -258,102 +304,34 @@ module DiasporaFederation
|
|
258
304
|
|
259
305
|
# Create simple node, fill it with text and append to root
|
260
306
|
def simple_node(doc, name, value)
|
261
|
-
|
262
|
-
Nokogiri::XML::Element.new(xml_name ? xml_name.to_s : name, doc).tap do |node|
|
307
|
+
Nokogiri::XML::Element.new(name.to_s, doc).tap do |node|
|
263
308
|
node.content = value.gsub(INVALID_XML_REGEX, "\uFFFD") unless value.empty?
|
264
309
|
end
|
265
310
|
end
|
266
311
|
|
267
|
-
#
|
268
|
-
#
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
# @param [Nokogiri::XML::Element] root_node xml nodes
|
274
|
-
# @return [Hash] entity data
|
275
|
-
private_class_method def self.entity_data(root_node)
|
276
|
-
class_props.map {|name, type|
|
277
|
-
value = parse_element_from_node(name, type, root_node)
|
278
|
-
[name, value] unless value.nil?
|
279
|
-
}.compact.to_h
|
280
|
-
end
|
281
|
-
|
282
|
-
# @param [String] name property name to parse
|
283
|
-
# @param [Class, Symbol] type target type to parse
|
284
|
-
# @param [Nokogiri::XML::Element] root_node XML node to parse
|
285
|
-
# @return [Object] parsed data
|
286
|
-
private_class_method def self.parse_element_from_node(name, type, root_node)
|
287
|
-
if type.instance_of?(Symbol)
|
288
|
-
parse_string_from_node(name, type, root_node)
|
289
|
-
elsif type.instance_of?(Array)
|
290
|
-
parse_array_from_node(type.first, root_node)
|
291
|
-
elsif type.ancestors.include?(Entity)
|
292
|
-
parse_entity_from_node(type, root_node)
|
293
|
-
end
|
294
|
-
end
|
312
|
+
# Generates a hash with entity properties which is put to the "entity_data"
|
313
|
+
# field of a JSON serialized object.
|
314
|
+
# @return [Hash] object properties in JSON format
|
315
|
+
def json_data
|
316
|
+
enriched_properties.map {|key, value|
|
317
|
+
type = self.class.class_props[key]
|
295
318
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
node = root_node.xpath(xml_names[name].to_s) if node.empty?
|
305
|
-
parse_string(type, node.first.text) if node.any?
|
306
|
-
end
|
307
|
-
|
308
|
-
# @param [Symbol] type target type to parse
|
309
|
-
# @param [String] text data as string
|
310
|
-
# @return [String, Boolean, Integer, Time] data
|
311
|
-
private_class_method def self.parse_string(type, text)
|
312
|
-
case type
|
313
|
-
when :timestamp
|
314
|
-
begin
|
315
|
-
Time.parse(text).utc
|
316
|
-
rescue
|
317
|
-
nil
|
319
|
+
if !value.nil? && type.instance_of?(Class) && value.respond_to?(:to_json)
|
320
|
+
entity_data = value.to_json
|
321
|
+
[key, entity_data] unless entity_data.nil?
|
322
|
+
elsif type.instance_of?(Array)
|
323
|
+
entity_data = value.nil? ? nil : value.map(&:to_json)
|
324
|
+
[key, entity_data] unless entity_data.nil?
|
325
|
+
else
|
326
|
+
[key, value]
|
318
327
|
end
|
319
|
-
|
320
|
-
text.to_i if text =~ /\A\d+\z/
|
321
|
-
when :boolean
|
322
|
-
return true if text =~ /\A(true|t|yes|y|1)\z/i
|
323
|
-
false if text =~ /\A(false|f|no|n|0)\z/i
|
324
|
-
else
|
325
|
-
text
|
326
|
-
end
|
327
|
-
end
|
328
|
-
|
329
|
-
# Create an entry in the data hash for the nested entity
|
330
|
-
#
|
331
|
-
# @param [Class] type target type to parse
|
332
|
-
# @param [Nokogiri::XML::Element] root_node XML node to parse
|
333
|
-
# @return [Entity] parsed child entity
|
334
|
-
private_class_method def self.parse_entity_from_node(type, root_node)
|
335
|
-
node = root_node.xpath(type.entity_name)
|
336
|
-
type.from_xml(node.first) if node.any? && node.first.children.any?
|
337
|
-
end
|
338
|
-
|
339
|
-
# Collect all nested children of that type and create an array in the data hash
|
340
|
-
#
|
341
|
-
# @param [Class] type target type to parse
|
342
|
-
# @param [Nokogiri::XML::Element] root_node XML node to parse
|
343
|
-
# @return [Array<Entity>] array with parsed child entities
|
344
|
-
private_class_method def self.parse_array_from_node(type, root_node)
|
345
|
-
node = root_node.xpath(type.entity_name)
|
346
|
-
node.select {|child| child.children.any? }.map {|child| type.from_xml(child) } unless node.empty?
|
328
|
+
}.compact.to_h
|
347
329
|
end
|
348
330
|
|
349
331
|
# Raised, if entity is not valid
|
350
332
|
class ValidationError < RuntimeError
|
351
333
|
end
|
352
334
|
|
353
|
-
# Raised, if the root node doesn't match the class name
|
354
|
-
class InvalidRootNode < RuntimeError
|
355
|
-
end
|
356
|
-
|
357
335
|
# Raised, if the entity name in the XML is invalid
|
358
336
|
class InvalidEntityName < RuntimeError
|
359
337
|
end
|
@@ -13,7 +13,7 @@ module DiasporaFederation
|
|
13
13
|
response = HttpClient.get(url)
|
14
14
|
raise "Failed to fetch #{url}: #{response.status}" unless response.success?
|
15
15
|
|
16
|
-
magic_env_xml = Nokogiri::XML
|
16
|
+
magic_env_xml = Nokogiri::XML(response.body).root
|
17
17
|
magic_env = Salmon::MagicEnvelope.unenvelop(magic_env_xml)
|
18
18
|
Receiver::Public.new(magic_env).receive
|
19
19
|
rescue => e
|
@@ -11,7 +11,7 @@ module DiasporaFederation
|
|
11
11
|
magic_env = if legacy
|
12
12
|
Salmon::Slap.from_xml(data)
|
13
13
|
else
|
14
|
-
magic_env_xml = Nokogiri::XML
|
14
|
+
magic_env_xml = Nokogiri::XML(data).root
|
15
15
|
Salmon::MagicEnvelope.unenvelop(magic_env_xml)
|
16
16
|
end
|
17
17
|
Public.new(magic_env).receive
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "typhoeus"
|
2
|
+
|
1
3
|
module DiasporaFederation
|
2
4
|
module Federation
|
3
5
|
module Sender
|
@@ -17,12 +19,21 @@ module DiasporaFederation
|
|
17
19
|
method: :post,
|
18
20
|
verbose: DiasporaFederation.http_verbose,
|
19
21
|
cainfo: DiasporaFederation.certificate_authorities,
|
20
|
-
forbid_reuse: true
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
forbid_reuse: true
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.xml_headers
|
27
|
+
@xml_headers ||= {
|
28
|
+
"Content-Type" => "application/magic-envelope+xml",
|
29
|
+
"User-Agent" => DiasporaFederation.http_user_agent
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.json_headers
|
34
|
+
@json_headers ||= {
|
35
|
+
"Content-Type" => "application/json",
|
36
|
+
"User-Agent" => DiasporaFederation.http_user_agent
|
26
37
|
}
|
27
38
|
end
|
28
39
|
|
@@ -36,13 +47,18 @@ module DiasporaFederation
|
|
36
47
|
@urls_to_retry = []
|
37
48
|
end
|
38
49
|
|
39
|
-
# Prepares and inserts job into the hydra queue
|
50
|
+
# Prepares and inserts a public MagicEnvelope job into the hydra queue
|
40
51
|
# @param [String] url the receive-url for the xml
|
41
|
-
# @param [String] xml xml
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
|
52
|
+
# @param [String] xml MagicEnvelope xml
|
53
|
+
def insert_magic_env_request(url, xml)
|
54
|
+
insert_job(url, HydraWrapper.hydra_opts.merge(body: xml, headers: HydraWrapper.xml_headers))
|
55
|
+
end
|
56
|
+
|
57
|
+
# Prepares and inserts a private encrypted MagicEnvelope job into the hydra queue
|
58
|
+
# @param [String] url the receive-url for the message
|
59
|
+
# @param [String] json encrypted MagicEnvelope json
|
60
|
+
def insert_enc_magic_env_request(url, json)
|
61
|
+
insert_job(url, HydraWrapper.hydra_opts.merge(body: json, headers: HydraWrapper.json_headers))
|
46
62
|
end
|
47
63
|
|
48
64
|
# Sends all queued messages
|
@@ -54,6 +70,15 @@ module DiasporaFederation
|
|
54
70
|
|
55
71
|
private
|
56
72
|
|
73
|
+
# Prepares and inserts job into the hydra queue
|
74
|
+
# @param [String] url the receive-url for the message
|
75
|
+
# @param [Hash] options request options
|
76
|
+
def insert_job(url, options)
|
77
|
+
request = Typhoeus::Request.new(url, options)
|
78
|
+
prepare_request(request)
|
79
|
+
hydra.queue(request)
|
80
|
+
end
|
81
|
+
|
57
82
|
# @return [Typhoeus::Hydra] hydra
|
58
83
|
def hydra
|
59
84
|
@hydra ||= Typhoeus::Hydra.new(max_concurrency: DiasporaFederation.http_concurrency)
|
@@ -11,7 +11,7 @@ module DiasporaFederation
|
|
11
11
|
# @return [Array<String>] url to retry
|
12
12
|
def self.public(sender_id, obj_str, urls, xml)
|
13
13
|
hydra = HydraWrapper.new(sender_id, obj_str)
|
14
|
-
urls.each {|url| hydra.
|
14
|
+
urls.each {|url| hydra.insert_magic_env_request(url, xml) }
|
15
15
|
hydra.send
|
16
16
|
end
|
17
17
|
|
@@ -23,7 +23,7 @@ module DiasporaFederation
|
|
23
23
|
# @return [Hash] targets to retry
|
24
24
|
def self.private(sender_id, obj_str, targets)
|
25
25
|
hydra = HydraWrapper.new(sender_id, obj_str)
|
26
|
-
targets.each {|url,
|
26
|
+
targets.each {|url, json| hydra.insert_enc_magic_env_request(url, json) }
|
27
27
|
hydra.send.map {|url| [url, targets[url]] }.to_h
|
28
28
|
end
|
29
29
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module DiasporaFederation
|
2
|
+
module Parsers
|
3
|
+
# +BaseParser+ is an abstract class which is used for defining parsers for different
|
4
|
+
# deserialization methods.
|
5
|
+
class BaseParser
|
6
|
+
# @param [Class] entity_type type of DiasporaFederation::Entity that we want to parse with that parser instance
|
7
|
+
def initialize(entity_type)
|
8
|
+
@entity_type = entity_type
|
9
|
+
end
|
10
|
+
|
11
|
+
# This method is used to parse input with a serialized object data. It returns
|
12
|
+
# a comprehensive data which must be enough to construct a DiasporaFederation::Entity instance.
|
13
|
+
#
|
14
|
+
# Since parser method output is normally passed to a .from_hash method of an entity
|
15
|
+
# as arguments using * operator, the parse method must return an array of a size matching the number
|
16
|
+
# of arguments of .from_hash method of the entity type we link with
|
17
|
+
# @abstract
|
18
|
+
def parse(*)
|
19
|
+
raise NotImplementedError.new("you must override this method when creating your own parser")
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# @param [Symbol] type target type to parse
|
25
|
+
# @param [String] text data as string
|
26
|
+
# @return [String, Boolean, Integer, Time] data
|
27
|
+
def parse_string(type, text)
|
28
|
+
case type
|
29
|
+
when :timestamp
|
30
|
+
begin
|
31
|
+
Time.parse(text).utc
|
32
|
+
rescue
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
when :integer
|
36
|
+
text.to_i if text =~ /\A\d+\z/
|
37
|
+
when :boolean
|
38
|
+
return true if text =~ /\A(true|t|yes|y|1)\z/i
|
39
|
+
false if text =~ /\A(false|f|no|n|0)\z/i
|
40
|
+
else
|
41
|
+
text
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def assert_parsability_of(entity_class)
|
46
|
+
return if entity_class == entity_type.entity_name
|
47
|
+
raise InvalidRootNode, "'#{entity_class}' can't be parsed by #{entity_type.name}"
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :entity_type
|
51
|
+
|
52
|
+
def class_properties
|
53
|
+
entity_type.class_props
|
54
|
+
end
|
55
|
+
|
56
|
+
# Raised, if the root node doesn't match the class name
|
57
|
+
class InvalidRootNode < RuntimeError
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module DiasporaFederation
|
2
|
+
module Parsers
|
3
|
+
# This is a parser of JSON serialized object. JSON object format is defined by
|
4
|
+
# JSON schema which is available at https://diaspora.github.io/diaspora_federation/schemas/federation_entities.json.
|
5
|
+
# TODO: We must publish the schema at a real URL
|
6
|
+
class JsonParser < BaseParser
|
7
|
+
# @see BaseParser#parse
|
8
|
+
# @param [Hash] json_hash A hash acquired by running JSON.parse with JSON serialized entity
|
9
|
+
# @return [Array[1]] comprehensive data for an entity instantiation
|
10
|
+
def parse(json_hash)
|
11
|
+
from_json_sanity_validation(json_hash)
|
12
|
+
parse_entity_data(json_hash["entity_data"])
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def parse_entity_data(entity_data)
|
18
|
+
hash = entity_data.map {|key, value|
|
19
|
+
property = entity_type.find_property_for_xml_name(key)
|
20
|
+
if property
|
21
|
+
type = entity_type.class_props[property]
|
22
|
+
[property, parse_element_from_value(type, entity_data[key])]
|
23
|
+
else
|
24
|
+
[key, value]
|
25
|
+
end
|
26
|
+
}.to_h
|
27
|
+
|
28
|
+
[hash]
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_element_from_value(type, value)
|
32
|
+
return if value.nil?
|
33
|
+
if %i[integer boolean timestamp].include?(type) && !value.is_a?(String)
|
34
|
+
value
|
35
|
+
elsif type.instance_of?(Symbol)
|
36
|
+
parse_string(type, value)
|
37
|
+
elsif type.instance_of?(Array)
|
38
|
+
raise DeserializationError, "Expected array for #{type}" unless value.respond_to?(:map)
|
39
|
+
value.map {|element|
|
40
|
+
type.first.from_json(element)
|
41
|
+
}
|
42
|
+
elsif type.ancestors.include?(Entity)
|
43
|
+
type.from_json(value)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def from_json_sanity_validation(json_hash)
|
48
|
+
missing = %w[entity_type entity_data].map {|prop|
|
49
|
+
prop if json_hash[prop].nil?
|
50
|
+
}.compact.join(", ")
|
51
|
+
raise DeserializationError, "Required properties are missing in JSON object: #{missing}" unless missing.empty?
|
52
|
+
assert_parsability_of(json_hash["entity_type"])
|
53
|
+
end
|
54
|
+
|
55
|
+
# Raised when the format of the input JSON data doesn't match the parser's expectations
|
56
|
+
class DeserializationError < RuntimeError
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module DiasporaFederation
|
2
|
+
module Parsers
|
3
|
+
# This is a parser of JSON serialized object, that is normally used for parsing data of relayables.
|
4
|
+
# Assumed format differs from the usual entity by additional "property_order" property which is used to
|
5
|
+
# compute signatures deterministically.
|
6
|
+
# Input JSON for this parser is expected to match "/definitions/relayable" subschema of the JSON schema at
|
7
|
+
# https://diaspora.github.io/diaspora_federation/schemas/federation_entities.json.
|
8
|
+
class RelayableJsonParser < JsonParser
|
9
|
+
# @see JsonParser#parse
|
10
|
+
# @see BaseParser#parse
|
11
|
+
# @return [Array[2]] comprehensive data for an entity instantiation
|
12
|
+
def parse(json_hash)
|
13
|
+
super.push(json_hash["property_order"])
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def from_json_sanity_validation(json_hash)
|
19
|
+
super
|
20
|
+
return unless json_hash["property_order"].nil?
|
21
|
+
raise DeserializationError, "Required property is missing in JSON object: property_order"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module DiasporaFederation
|
2
|
+
module Parsers
|
3
|
+
# This is a parser of XML serialized object that is normally used for parsing data of relayables.
|
4
|
+
# Explanations about the XML data format can be found
|
5
|
+
# {https://diaspora.github.io/diaspora_federation/federation/xml_serialization.html here}.
|
6
|
+
# Specific features of relayables are described
|
7
|
+
# {https://diaspora.github.io/diaspora_federation/federation/relayable.html here}.
|
8
|
+
#
|
9
|
+
# @see https://diaspora.github.io/diaspora_federation/federation/xml_serialization.html XML Serialization
|
10
|
+
# documentation
|
11
|
+
# @see https://diaspora.github.io/diaspora_federation/federation/relayable.html Relayable documentation
|
12
|
+
class RelayableXmlParser < XmlParser
|
13
|
+
# @see XmlParser#parse
|
14
|
+
# @see BaseParser#parse
|
15
|
+
# @return [Array[2]] comprehensive data for an entity instantiation
|
16
|
+
def parse(*args)
|
17
|
+
hash = super[0]
|
18
|
+
[hash, hash.keys]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module DiasporaFederation
|
2
|
+
module Parsers
|
3
|
+
# This is a parser of XML serialized object.
|
4
|
+
# Explanations about the XML data format can be found
|
5
|
+
# {https://diaspora.github.io/diaspora_federation/federation/xml_serialization.html here}.
|
6
|
+
# @see https://diaspora.github.io/diaspora_federation/federation/xml_serialization.html XML Serialization
|
7
|
+
# documentation
|
8
|
+
class XmlParser < BaseParser
|
9
|
+
# @see BaseParser#parse
|
10
|
+
# @param [Nokogiri::XML::Element] root_node root XML node of the XML representation of the entity
|
11
|
+
# @return [Array[1]] comprehensive data for an entity instantiation
|
12
|
+
def parse(root_node)
|
13
|
+
from_xml_sanity_validation(root_node)
|
14
|
+
|
15
|
+
hash = root_node.element_children.map {|child|
|
16
|
+
xml_name = child.name
|
17
|
+
property = entity_type.find_property_for_xml_name(xml_name)
|
18
|
+
if property
|
19
|
+
type = class_properties[property]
|
20
|
+
value = parse_element_from_node(xml_name, type, root_node)
|
21
|
+
[property, value]
|
22
|
+
else
|
23
|
+
[xml_name, child.text]
|
24
|
+
end
|
25
|
+
}.to_h
|
26
|
+
|
27
|
+
[hash]
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# @param [String] name property name to parse
|
33
|
+
# @param [Class, Symbol] type target type to parse
|
34
|
+
# @param [Nokogiri::XML::Element] root_node XML node to parse
|
35
|
+
# @return [Object] parsed data
|
36
|
+
def parse_element_from_node(name, type, root_node)
|
37
|
+
if type.instance_of?(Symbol)
|
38
|
+
parse_string_from_node(name, type, root_node)
|
39
|
+
elsif type.instance_of?(Array)
|
40
|
+
parse_array_from_node(type.first, root_node)
|
41
|
+
elsif type.ancestors.include?(Entity)
|
42
|
+
parse_entity_from_node(type, root_node)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Create simple entry in data hash
|
47
|
+
#
|
48
|
+
# @param [String] name xml tag to parse
|
49
|
+
# @param [Class, Symbol] type target type to parse
|
50
|
+
# @param [Nokogiri::XML::Element] root_node XML root_node to parse
|
51
|
+
# @return [String] data
|
52
|
+
def parse_string_from_node(name, type, root_node)
|
53
|
+
node = root_node.xpath(name.to_s)
|
54
|
+
node = root_node.xpath(xml_names[name].to_s) if node.empty?
|
55
|
+
parse_string(type, node.first.text) if node.any?
|
56
|
+
end
|
57
|
+
|
58
|
+
# Create an entry in the data hash for the nested entity
|
59
|
+
#
|
60
|
+
# @param [Class] type target type to parse
|
61
|
+
# @param [Nokogiri::XML::Element] root_node XML node to parse
|
62
|
+
# @return [Entity] parsed child entity
|
63
|
+
def parse_entity_from_node(type, root_node)
|
64
|
+
node = root_node.xpath(type.entity_name)
|
65
|
+
type.from_xml(node.first) if node.any? && node.first.children.any?
|
66
|
+
end
|
67
|
+
|
68
|
+
# Collect all nested children of that type and create an array in the data hash
|
69
|
+
#
|
70
|
+
# @param [Class] type target type to parse
|
71
|
+
# @param [Nokogiri::XML::Element] root_node XML node to parse
|
72
|
+
# @return [Array<Entity>] array with parsed child entities
|
73
|
+
def parse_array_from_node(type, root_node)
|
74
|
+
node = root_node.xpath(type.entity_name)
|
75
|
+
node.select {|child| child.children.any? }.map {|child| type.from_xml(child) } unless node.empty?
|
76
|
+
end
|
77
|
+
|
78
|
+
def from_xml_sanity_validation(root_node)
|
79
|
+
raise ArgumentError, "only Nokogiri::XML::Element allowed" unless root_node.instance_of?(Nokogiri::XML::Element)
|
80
|
+
assert_parsability_of(root_node.name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module DiasporaFederation
|
2
|
+
# This namespace contains parsers which are used to deserialize federation entities
|
3
|
+
# objects from supported formats (XML, JSON) to objects of DiasporaFederation::Entity
|
4
|
+
# classes
|
5
|
+
module Parsers
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require "diaspora_federation/parsers/base_parser"
|
10
|
+
require "diaspora_federation/parsers/json_parser"
|
11
|
+
require "diaspora_federation/parsers/xml_parser"
|
12
|
+
require "diaspora_federation/parsers/relayable_json_parser"
|
13
|
+
require "diaspora_federation/parsers/relayable_xml_parser"
|
@@ -54,7 +54,7 @@ module DiasporaFederation
|
|
54
54
|
key = encoded_key.map {|k, v| [k, Base64.decode64(v)] }.to_h
|
55
55
|
|
56
56
|
xml = AES.decrypt(encrypted_json["encrypted_magic_envelope"], key["key"], key["iv"])
|
57
|
-
Nokogiri::XML
|
57
|
+
Nokogiri::XML(xml).root
|
58
58
|
end
|
59
59
|
end
|
60
60
|
end
|