diaspora_federation 0.0.12 → 0.0.13

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/lib/diaspora_federation.rb +103 -18
  3. data/lib/diaspora_federation/discovery/discovery.rb +1 -1
  4. data/lib/diaspora_federation/discovery/h_card.rb +4 -5
  5. data/lib/diaspora_federation/discovery/host_meta.rb +1 -1
  6. data/lib/diaspora_federation/discovery/web_finger.rb +8 -8
  7. data/lib/diaspora_federation/discovery/xrd_document.rb +6 -7
  8. data/lib/diaspora_federation/entities.rb +21 -10
  9. data/lib/diaspora_federation/entities/account_deletion.rb +7 -3
  10. data/lib/diaspora_federation/entities/comment.rb +13 -10
  11. data/lib/diaspora_federation/entities/contact.rb +29 -0
  12. data/lib/diaspora_federation/entities/conversation.rb +5 -6
  13. data/lib/diaspora_federation/entities/like.rb +10 -18
  14. data/lib/diaspora_federation/entities/message.rb +6 -12
  15. data/lib/diaspora_federation/entities/participation.rb +8 -16
  16. data/lib/diaspora_federation/entities/person.rb +6 -2
  17. data/lib/diaspora_federation/entities/photo.rb +3 -3
  18. data/lib/diaspora_federation/entities/poll_participation.rb +6 -12
  19. data/lib/diaspora_federation/entities/post.rb +37 -0
  20. data/lib/diaspora_federation/entities/profile.rb +7 -3
  21. data/lib/diaspora_federation/entities/relayable.rb +169 -65
  22. data/lib/diaspora_federation/entities/relayable_retraction.rb +33 -32
  23. data/lib/diaspora_federation/entities/request.rb +20 -6
  24. data/lib/diaspora_federation/entities/reshare.rb +5 -27
  25. data/lib/diaspora_federation/entities/retraction.rb +6 -6
  26. data/lib/diaspora_federation/entities/signed_retraction.rb +32 -26
  27. data/lib/diaspora_federation/entities/status_message.rb +2 -22
  28. data/lib/diaspora_federation/entity.rb +137 -38
  29. data/lib/diaspora_federation/federation.rb +9 -0
  30. data/lib/diaspora_federation/federation/fetcher.rb +26 -0
  31. data/lib/diaspora_federation/federation/receiver.rb +41 -0
  32. data/lib/diaspora_federation/federation/receiver/abstract_receiver.rb +35 -0
  33. data/lib/diaspora_federation/federation/receiver/exceptions.rb +13 -0
  34. data/lib/diaspora_federation/federation/receiver/private.rb +15 -0
  35. data/lib/diaspora_federation/federation/receiver/public.rb +9 -0
  36. data/lib/diaspora_federation/federation/sender.rb +33 -0
  37. data/lib/diaspora_federation/federation/sender/hydra_wrapper.rb +92 -0
  38. data/lib/diaspora_federation/{fetcher.rb → http_client.rb} +6 -6
  39. data/lib/diaspora_federation/properties_dsl.rb +51 -14
  40. data/lib/diaspora_federation/salmon.rb +2 -1
  41. data/lib/diaspora_federation/salmon/aes.rb +1 -1
  42. data/lib/diaspora_federation/salmon/encrypted_magic_envelope.rb +61 -0
  43. data/lib/diaspora_federation/salmon/encrypted_slap.rb +69 -50
  44. data/lib/diaspora_federation/salmon/exceptions.rb +8 -14
  45. data/lib/diaspora_federation/salmon/magic_envelope.rb +80 -39
  46. data/lib/diaspora_federation/salmon/slap.rb +20 -51
  47. data/lib/diaspora_federation/salmon/xml_payload.rb +5 -104
  48. data/lib/diaspora_federation/validators.rb +22 -16
  49. data/lib/diaspora_federation/validators/account_deletion_validator.rb +1 -1
  50. data/lib/diaspora_federation/validators/comment_validator.rb +0 -4
  51. data/lib/diaspora_federation/validators/contact_validator.rb +13 -0
  52. data/lib/diaspora_federation/validators/conversation_validator.rb +2 -2
  53. data/lib/diaspora_federation/validators/like_validator.rb +1 -3
  54. data/lib/diaspora_federation/validators/message_validator.rb +0 -4
  55. data/lib/diaspora_federation/validators/participation_validator.rb +1 -5
  56. data/lib/diaspora_federation/validators/person_validator.rb +1 -1
  57. data/lib/diaspora_federation/validators/photo_validator.rb +2 -2
  58. data/lib/diaspora_federation/validators/poll_participation_validator.rb +0 -4
  59. data/lib/diaspora_federation/validators/profile_validator.rb +1 -1
  60. data/lib/diaspora_federation/validators/relayable_retraction_validator.rb +1 -1
  61. data/lib/diaspora_federation/validators/relayable_validator.rb +2 -0
  62. data/lib/diaspora_federation/validators/request_validator.rb +3 -2
  63. data/lib/diaspora_federation/validators/reshare_validator.rb +3 -3
  64. data/lib/diaspora_federation/validators/retraction_validator.rb +2 -2
  65. data/lib/diaspora_federation/validators/rules/guid.rb +16 -7
  66. data/lib/diaspora_federation/validators/signed_retraction_validator.rb +1 -1
  67. data/lib/diaspora_federation/validators/status_message_validator.rb +2 -2
  68. data/lib/diaspora_federation/version.rb +1 -1
  69. metadata +20 -11
  70. data/lib/diaspora_federation/receiver.rb +0 -28
  71. data/lib/diaspora_federation/receiver/private.rb +0 -19
  72. data/lib/diaspora_federation/receiver/public.rb +0 -13
  73. data/lib/diaspora_federation/signing.rb +0 -56
@@ -0,0 +1,9 @@
1
+ module DiasporaFederation
2
+ # This module contains the federation logic
3
+ module Federation
4
+ end
5
+ end
6
+
7
+ require "diaspora_federation/federation/fetcher"
8
+ require "diaspora_federation/federation/receiver"
9
+ require "diaspora_federation/federation/sender"
@@ -0,0 +1,26 @@
1
+ module DiasporaFederation
2
+ module Federation
3
+ # this module is for fetching entities from other pods
4
+ module Fetcher
5
+ # fetches a public entity from a remote pod
6
+ # @param [String] author the diaspora ID of the author of the entity
7
+ # @param [Symbol, String] entity_type snake_case version of the entity class
8
+ # @param [String] guid guid of the entity to fetch
9
+ def self.fetch_public(author, entity_type, guid)
10
+ url = DiasporaFederation.callbacks.trigger(:fetch_person_url_to, author, "/fetch/#{entity_type}/#{guid}")
11
+ response = HttpClient.get(url)
12
+ raise "Failed to fetch #{url}: #{response.status}" unless response.success?
13
+
14
+ magic_env_xml = Nokogiri::XML::Document.parse(response.body).root
15
+ magic_env = Salmon::MagicEnvelope.unenvelop(magic_env_xml)
16
+ Receiver::Public.new(magic_env).receive
17
+ rescue => e
18
+ raise NotFetchable, "Failed to fetch #{entity_type}:#{guid} from #{author}: #{e.class}: #{e.message}"
19
+ end
20
+
21
+ # Raised, if the entity is not fetchable
22
+ class NotFetchable < RuntimeError
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ module DiasporaFederation
2
+ module Federation
3
+ # this module is for parse and receive entities.
4
+ module Receiver
5
+ # receive a public message
6
+ # @param [String] data message to receive
7
+ # @param [Boolean] legacy use old slap parser
8
+ def self.receive_public(data, legacy=false)
9
+ magic_env = if legacy
10
+ Salmon::Slap.from_xml(data)
11
+ else
12
+ magic_env_xml = Nokogiri::XML::Document.parse(data).root
13
+ Salmon::MagicEnvelope.unenvelop(magic_env_xml)
14
+ end
15
+ Public.new(magic_env).receive
16
+ end
17
+
18
+ # receive a private message
19
+ # @param [String] data message to receive
20
+ # @param [OpenSSL::PKey::RSA] recipient_private_key recipient private key to decrypt the message
21
+ # @param [Object] recipient_id the identifier to persist the entity for the correct user,
22
+ # see +receive_entity+ callback
23
+ # @param [Boolean] legacy use old slap parser
24
+ def self.receive_private(data, recipient_private_key, recipient_id, legacy=false)
25
+ raise ArgumentError, "no recipient key provided" unless recipient_private_key.instance_of?(OpenSSL::PKey::RSA)
26
+ magic_env = if legacy
27
+ Salmon::EncryptedSlap.from_xml(data, recipient_private_key)
28
+ else
29
+ magic_env_xml = Salmon::EncryptedMagicEnvelope.decrypt(data, recipient_private_key)
30
+ Salmon::MagicEnvelope.unenvelop(magic_env_xml)
31
+ end
32
+ Private.new(magic_env, recipient_id).receive
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ require "diaspora_federation/federation/receiver/exceptions"
39
+ require "diaspora_federation/federation/receiver/abstract_receiver"
40
+ require "diaspora_federation/federation/receiver/public"
41
+ require "diaspora_federation/federation/receiver/private"
@@ -0,0 +1,35 @@
1
+ module DiasporaFederation
2
+ module Federation
3
+ module Receiver
4
+ # common functionality for receivers
5
+ class AbstractReceiver
6
+ # create a new receiver
7
+ # @param [MagicEnvelope] magic_envelope the received magic envelope
8
+ # @param [Object] recipient_id the identifier of the recipient of a private message
9
+ def initialize(magic_envelope, recipient_id=nil)
10
+ @entity = magic_envelope.payload
11
+ @sender = magic_envelope.sender
12
+ @recipient_id = recipient_id
13
+ end
14
+
15
+ # validate and receive the entity
16
+ def receive
17
+ validate
18
+ DiasporaFederation.callbacks.trigger(:receive_entity, entity, recipient_id)
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :entity, :sender, :recipient_id
24
+
25
+ def validate
26
+ raise InvalidSender unless sender_valid?
27
+ end
28
+
29
+ def sender_valid?
30
+ sender == entity.author # TODO: handle sender of relayables
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,13 @@
1
+ module DiasporaFederation
2
+ module Federation
3
+ module Receiver
4
+ # Raised, if the sender of the {Salmon::MagicEnvelope} is not allowed to send the entity.
5
+ class InvalidSender < RuntimeError
6
+ end
7
+
8
+ # Raised, if receiving a private message without recipient.
9
+ class RecipientRequired < RuntimeError
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module DiasporaFederation
2
+ module Federation
3
+ module Receiver
4
+ # receiver for private entities
5
+ class Private < AbstractReceiver
6
+ private
7
+
8
+ def validate
9
+ raise RecipientRequired if recipient_id.nil?
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module DiasporaFederation
2
+ module Federation
3
+ module Receiver
4
+ # receiver for public entities
5
+ class Public < AbstractReceiver
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ module DiasporaFederation
2
+ module Federation
3
+ # Federation logic to send messages to other pods
4
+ module Sender
5
+ # Send a public message to all urls
6
+ #
7
+ # @param [String] sender_id sender diaspora-ID
8
+ # @param [String] obj_str object string representation for logging (e.g. type@guid)
9
+ # @param [Array<String>] urls receive-urls from pods
10
+ # @param [String] xml salmon-xml
11
+ # @return [Array<String>] url to retry
12
+ def self.public(sender_id, obj_str, urls, xml)
13
+ hydra = HydraWrapper.new(sender_id, obj_str)
14
+ urls.each {|url| hydra.insert_job(url, xml) }
15
+ hydra.send
16
+ end
17
+
18
+ # Send a private message to receive-urls
19
+ #
20
+ # @param [String] sender_id sender diaspora-ID
21
+ # @param [String] obj_str object string representation for logging (e.g. type@guid)
22
+ # @param [Hash] targets Hash with receive-urls (key) of peoples with encrypted salmon-xml for them (value)
23
+ # @return [Hash] targets to retry
24
+ def self.private(sender_id, obj_str, targets)
25
+ hydra = HydraWrapper.new(sender_id, obj_str)
26
+ targets.each {|url, xml| hydra.insert_job(url, xml) }
27
+ Hash[hydra.send.map {|url| [url, targets[url]] }]
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ require "diaspora_federation/federation/sender/hydra_wrapper"
@@ -0,0 +1,92 @@
1
+ module DiasporaFederation
2
+ module Federation
3
+ module Sender
4
+ # A wrapper for [Typhoeus::Hydra]
5
+ #
6
+ # Uses parallel http requests to send out the salmon-messages
7
+ class HydraWrapper
8
+ include Logging
9
+
10
+ # Hydra default opts
11
+ # @return [Hash] hydra opts
12
+ def self.hydra_opts
13
+ @hydra_opts ||= {
14
+ maxredirs: DiasporaFederation.http_redirect_limit,
15
+ timeout: DiasporaFederation.http_timeout,
16
+ method: :post,
17
+ verbose: DiasporaFederation.http_verbose,
18
+ cainfo: DiasporaFederation.certificate_authorities,
19
+ headers: {
20
+ "Expect" => "",
21
+ "Transfer-Encoding" => "",
22
+ "User-Agent" => DiasporaFederation.http_user_agent
23
+ }
24
+ }
25
+ end
26
+
27
+ # Create a new instance for a message
28
+ #
29
+ # @param [String] sender_id sender diaspora-ID
30
+ # @param [String] obj_str object string representation for logging (e.g. type@guid)
31
+ def initialize(sender_id, obj_str)
32
+ @sender_id = sender_id
33
+ @obj_str = obj_str
34
+ @urls_to_retry = []
35
+ end
36
+
37
+ # Prepares and inserts job into the hydra queue
38
+ # @param [String] url the receive-url for the xml
39
+ # @param [String] xml xml salmon message
40
+ def insert_job(url, xml)
41
+ request = Typhoeus::Request.new(url, HydraWrapper.hydra_opts.merge(body: {xml: xml}))
42
+ prepare_request(request)
43
+ hydra.queue(request)
44
+ end
45
+
46
+ # Sends all queued messages
47
+ # @return [Array<String>] urls to retry
48
+ def send
49
+ hydra.run
50
+ @urls_to_retry
51
+ end
52
+
53
+ private
54
+
55
+ # @return [Typhoeus::Hydra] hydra
56
+ def hydra
57
+ @hydra ||= Typhoeus::Hydra.new(max_concurrency: DiasporaFederation.http_concurrency)
58
+ end
59
+
60
+ # Logic for after complete
61
+ # @param [Typhoeus::Request] request
62
+ def prepare_request(request)
63
+ request.on_complete do |response|
64
+ DiasporaFederation.callbacks.trigger(:update_pod, pod_url(response.effective_url), status(response))
65
+
66
+ success = response.success?
67
+ log_line = "success=#{success} sender=#{@sender_id} obj=#{@obj_str} url=#{response.effective_url} " \
68
+ "message=#{response.return_code} code=#{response.response_code} time=#{response.total_time}"
69
+ if success
70
+ logger.info(log_line)
71
+ else
72
+ logger.warn(log_line)
73
+
74
+ @urls_to_retry << request.url
75
+ end
76
+ end
77
+ end
78
+
79
+ # Get the pod root-url from the send-url
80
+ # @param [String] url
81
+ # @return [String] pod root-url
82
+ def pod_url(url)
83
+ URI.parse(url).tap {|uri| uri.path = "/" }.to_s
84
+ end
85
+
86
+ def status(res)
87
+ res.return_code == :ok ? res.response_code : res.return_code
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -2,11 +2,11 @@ require "faraday"
2
2
  require "faraday_middleware/response/follow_redirects"
3
3
 
4
4
  module DiasporaFederation
5
- # A wrapper for {https://github.com/lostisland/faraday Faraday} used for
6
- # fetching
5
+ # A wrapper for {https://github.com/lostisland/faraday Faraday}.
7
6
  #
8
7
  # @see Discovery::Discovery
9
- class Fetcher
8
+ # @see Federation::Fetcher
9
+ class HttpClient
10
10
  # Perform a GET request
11
11
  #
12
12
  # @param [String] uri the URI
@@ -25,16 +25,16 @@ module DiasporaFederation
25
25
 
26
26
  def self.create_default_connection
27
27
  options = {
28
- request: {timeout: 30},
28
+ request: {timeout: DiasporaFederation.http_timeout},
29
29
  ssl: {ca_file: DiasporaFederation.certificate_authorities}
30
30
  }
31
31
 
32
32
  @connection = Faraday::Connection.new(options) do |builder|
33
- builder.use FaradayMiddleware::FollowRedirects, limit: 4
33
+ builder.use FaradayMiddleware::FollowRedirects, limit: DiasporaFederation.http_redirect_limit
34
34
  builder.adapter Faraday.default_adapter
35
35
  end
36
36
 
37
- @connection.headers["User-Agent"] = "DiasporaFederation/#{DiasporaFederation::VERSION}"
37
+ @connection.headers["User-Agent"] = DiasporaFederation.http_user_agent
38
38
  end
39
39
  private_class_method :create_default_connection
40
40
  end
@@ -7,12 +7,13 @@ module DiasporaFederation
7
7
  # property :optional, default: false
8
8
  # property :dynamic_default, default: -> { Time.now }
9
9
  # property :another_prop, xml_name: :another_name
10
+ # property :original_prop, alias: :alias_prop
10
11
  # entity :nested, NestedEntity
11
12
  # entity :multiple, [OtherEntity]
12
13
  module PropertiesDSL
13
- # @return [Array<Hash>] hash of declared entity properties
14
+ # @return [Hash] hash of declared entity properties
14
15
  def class_props
15
- @class_props ||= []
16
+ @class_props ||= {}
16
17
  end
17
18
 
18
19
  # Define a generic (string-type) property
@@ -42,32 +43,48 @@ module DiasporaFederation
42
43
  # Return array of missing required property names
43
44
  # @return [Array<Symbol>] missing required property names
44
45
  def missing_props(args)
45
- class_prop_names - default_props.keys - args.keys
46
+ class_props.keys - default_props.keys - args.keys
46
47
  end
47
48
 
48
49
  # Return a new hash of default values, with dynamic values
49
50
  # resolved on each call
50
51
  # @return [Hash] default values
51
52
  def default_values
52
- default_props.each_with_object({}) { |(name, prop), hash|
53
+ default_props.each_with_object({}) {|(name, prop), hash|
53
54
  hash[name] = prop.respond_to?(:call) ? prop.call : prop
54
55
  }
55
56
  end
56
57
 
57
- # Returns all nested Entities
58
- # @return [Array<Hash>] nested properties
59
- def nested_class_props
60
- @nested_class_props ||= class_props.select {|p| p[:type] != String }
58
+ # @param [Hash] data entity data
59
+ # @return [Hash] hash with resolved aliases
60
+ def resolv_aliases(data)
61
+ Hash[data.map {|name, value|
62
+ if class_prop_aliases.has_key? name
63
+ prop_name = class_prop_aliases[name]
64
+ raise InvalidData, "only use '#{name}' OR '#{prop_name}'" if data.has_key? prop_name
65
+ [prop_name, value]
66
+ else
67
+ [name, value]
68
+ end
69
+ }]
70
+ end
71
+
72
+ # @return [Symbol] alias for the xml-generation/parsing
73
+ # @deprecated
74
+ def xml_names
75
+ @xml_names ||= {}
61
76
  end
62
77
 
63
- # Returns all property names
64
- # @return [Array] property names
65
- def class_prop_names
66
- @class_prop_names ||= class_props.map {|p| p[:name] }
78
+ # finds a property by +xml_name+ or +name+
79
+ # @param [String] xml_name name of the property from the received xml
80
+ # @return [Hash] the property data
81
+ def find_property_for_xml_name(xml_name)
82
+ class_props.keys.find {|name| name.to_s == xml_name || xml_names[name].to_s == xml_name }
67
83
  end
68
84
 
69
85
  private
70
86
 
87
+ # @deprecated
71
88
  def determine_xml_name(name, type, opts={})
72
89
  raise ArgumentError, "xml_name is not supported for nested entities" if type != String && opts.has_key?(:xml_name)
73
90
 
@@ -88,10 +105,13 @@ module DiasporaFederation
88
105
  def define_property(name, type, opts={})
89
106
  raise InvalidName unless name_valid?(name)
90
107
 
91
- class_props << {name: name, xml_name: determine_xml_name(name, type, opts), type: type}
108
+ class_props[name] = type
92
109
  default_props[name] = opts[:default] if opts.has_key? :default
110
+ xml_names[name] = determine_xml_name(name, type, opts)
93
111
 
94
112
  instance_eval { attr_reader name }
113
+
114
+ define_alias(name, opts[:alias]) if opts.has_key? :alias
95
115
  end
96
116
 
97
117
  # checks if the name is a +Symbol+ or a +String+
@@ -105,7 +125,7 @@ module DiasporaFederation
105
125
  # @param [Class] type the type to check
106
126
  # @return [Boolean]
107
127
  def type_valid?(type)
108
- [type].flatten.all? { |type|
128
+ [type].flatten.all? {|type|
109
129
  type.respond_to?(:ancestors) && type.ancestors.include?(Entity)
110
130
  }
111
131
  end
@@ -114,6 +134,19 @@ module DiasporaFederation
114
134
  @default_props ||= {}
115
135
  end
116
136
 
137
+ # Returns all alias mappings
138
+ # @return [Hash] alias properties
139
+ def class_prop_aliases
140
+ @class_prop_aliases ||= {}
141
+ end
142
+
143
+ # @param [Symbol] name property name
144
+ # @param [Symbol] alias_name alias name
145
+ def define_alias(name, alias_name)
146
+ class_prop_aliases[alias_name] = name
147
+ instance_eval { alias_method alias_name, name }
148
+ end
149
+
117
150
  # Raised, if the name is of an unexpected type
118
151
  class InvalidName < RuntimeError
119
152
  end
@@ -121,5 +154,9 @@ module DiasporaFederation
121
154
  # Raised, if the type is of an unexpected type
122
155
  class InvalidType < RuntimeError
123
156
  end
157
+
158
+ # Raised, if the data contains property twice (with name AND alias)
159
+ class InvalidData < RuntimeError
160
+ end
124
161
  end
125
162
  end