diaspora_federation 0.0.12 → 0.0.13

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