diaspora_federation 0.0.13 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -32
  3. data/lib/diaspora_federation.rb +9 -31
  4. data/lib/diaspora_federation/discovery/h_card.rb +8 -15
  5. data/lib/diaspora_federation/discovery/host_meta.rb +2 -4
  6. data/lib/diaspora_federation/discovery/web_finger.rb +11 -11
  7. data/lib/diaspora_federation/discovery/xrd_document.rb +4 -8
  8. data/lib/diaspora_federation/entities.rb +2 -0
  9. data/lib/diaspora_federation/entities/account_deletion.rb +5 -0
  10. data/lib/diaspora_federation/entities/comment.rb +3 -6
  11. data/lib/diaspora_federation/entities/contact.rb +5 -0
  12. data/lib/diaspora_federation/entities/conversation.rb +10 -1
  13. data/lib/diaspora_federation/entities/message.rb +29 -4
  14. data/lib/diaspora_federation/entities/participation.rb +24 -0
  15. data/lib/diaspora_federation/entities/poll_participation.rb +3 -6
  16. data/lib/diaspora_federation/entities/profile.rb +5 -0
  17. data/lib/diaspora_federation/entities/related_entity.rb +33 -0
  18. data/lib/diaspora_federation/entities/relayable.rb +55 -40
  19. data/lib/diaspora_federation/entities/relayable_retraction.rb +21 -12
  20. data/lib/diaspora_federation/entities/request.rb +6 -2
  21. data/lib/diaspora_federation/entities/reshare.rb +5 -0
  22. data/lib/diaspora_federation/entities/retraction.rb +37 -0
  23. data/lib/diaspora_federation/entities/signed_retraction.rb +16 -5
  24. data/lib/diaspora_federation/entities/status_message.rb +11 -0
  25. data/lib/diaspora_federation/entity.rb +73 -30
  26. data/lib/diaspora_federation/federation/fetcher.rb +11 -1
  27. data/lib/diaspora_federation/federation/receiver.rb +10 -0
  28. data/lib/diaspora_federation/federation/receiver/abstract_receiver.rb +18 -4
  29. data/lib/diaspora_federation/federation/receiver/exceptions.rb +4 -0
  30. data/lib/diaspora_federation/federation/receiver/public.rb +10 -0
  31. data/lib/diaspora_federation/federation/sender.rb +1 -1
  32. data/lib/diaspora_federation/http_client.rb +1 -2
  33. data/lib/diaspora_federation/logging.rb +6 -0
  34. data/lib/diaspora_federation/properties_dsl.rb +4 -2
  35. data/lib/diaspora_federation/salmon/encrypted_magic_envelope.rb +2 -2
  36. data/lib/diaspora_federation/salmon/encrypted_slap.rb +3 -5
  37. data/lib/diaspora_federation/salmon/exceptions.rb +1 -1
  38. data/lib/diaspora_federation/salmon/magic_envelope.rb +16 -17
  39. data/lib/diaspora_federation/salmon/slap.rb +1 -2
  40. data/lib/diaspora_federation/salmon/xml_payload.rb +1 -2
  41. data/lib/diaspora_federation/validators.rb +2 -0
  42. data/lib/diaspora_federation/validators/conversation_validator.rb +2 -0
  43. data/lib/diaspora_federation/validators/message_validator.rb +2 -2
  44. data/lib/diaspora_federation/validators/participation_validator.rb +3 -2
  45. data/lib/diaspora_federation/validators/poll_validator.rb +1 -0
  46. data/lib/diaspora_federation/validators/related_entity_validator.rb +12 -0
  47. data/lib/diaspora_federation/validators/relayable_retraction_validator.rb +1 -1
  48. data/lib/diaspora_federation/validators/relayable_validator.rb +1 -0
  49. data/lib/diaspora_federation/validators/retraction_validator.rb +1 -1
  50. data/lib/diaspora_federation/validators/rules/diaspora_id.rb +8 -11
  51. data/lib/diaspora_federation/validators/rules/diaspora_id_count.rb +1 -1
  52. data/lib/diaspora_federation/validators/signed_retraction_validator.rb +1 -1
  53. data/lib/diaspora_federation/validators/status_message_validator.rb +2 -0
  54. data/lib/diaspora_federation/validators/web_finger_validator.rb +2 -2
  55. data/lib/diaspora_federation/version.rb +1 -1
  56. metadata +9 -7
@@ -26,10 +26,35 @@ module DiasporaFederation
26
26
  # @return [String] conversation guid
27
27
  property :conversation_guid
28
28
 
29
- # The {Message} parent is a {Conversation}
30
- # @return [String] parent type
31
- def parent_type
32
- "Conversation"
29
+ # It is only valid to receive a {Message} from the author itself,
30
+ # or from the author of the parent {Conversation} if the author signature is valid.
31
+ # @deprecated remove after {Message} doesn't include {Relayable} anymore
32
+ def sender_valid?(sender)
33
+ sender == author || (sender == parent_author && verify_author_signature)
34
+ end
35
+
36
+ private
37
+
38
+ # @deprecated remove after {Message} doesn't include {Relayable} anymore
39
+ def verify_author_signature
40
+ verify_signature(author, :author_signature)
41
+ true
42
+ end
43
+
44
+ # @deprecated remove after {Message} doesn't include {Relayable} anymore
45
+ def parent_author
46
+ parent = DiasporaFederation.callbacks.trigger(:fetch_related_entity, "Conversation", conversation_guid)
47
+ raise Federation::Fetcher::NotFetchable, "parent of #{self} not found" unless parent
48
+ parent.author
49
+ end
50
+
51
+ # Default implementation, don't verify signatures for a {Message}.
52
+ # @see Entity.populate_entity
53
+ # @deprecated remove after {Message} doesn't include {Relayable} anymore
54
+ # @param [Nokogiri::XML::Element] root_node xml nodes
55
+ # @return [Entity] instance
56
+ private_class_method def self.populate_entity(root_node)
57
+ new({parent_guid: nil, parent: nil}.merge(entity_data(root_node)))
33
58
  end
34
59
  end
35
60
  end
@@ -15,6 +15,30 @@ module DiasporaFederation
15
15
  # currently only "Post" is supported.
16
16
  # @return [String] parent type
17
17
  property :parent_type, xml_name: :target_type
18
+
19
+ # It is only valid to receive a {Participation} from the author itself.
20
+ # @deprecated remove after {Participation} doesn't include {Relayable} anymore
21
+ def sender_valid?(sender)
22
+ sender == author
23
+ end
24
+
25
+ # validates that the parent exists and the parent author is local
26
+ def validate_parent
27
+ parent = DiasporaFederation.callbacks.trigger(:fetch_related_entity, parent_type, parent_guid)
28
+ raise ParentNotLocal, "obj=#{self}" unless parent && parent.local
29
+ end
30
+
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
34
+ # @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)
37
+ end
38
+
39
+ # Raised, if the parent is not owned by the receiving pod.
40
+ class ParentNotLocal < RuntimeError
41
+ end
18
42
  end
19
43
  end
20
44
  end
@@ -8,6 +8,9 @@ module DiasporaFederation
8
8
  # @deprecated
9
9
  LEGACY_SIGNATURE_ORDER = %i(guid parent_guid author poll_answer_guid).freeze
10
10
 
11
+ # The {PollParticipation} parent is a {Poll}
12
+ PARENT_TYPE = "Poll".freeze
13
+
11
14
  include Relayable
12
15
 
13
16
  # @!attribute [r] poll_answer_guid
@@ -15,12 +18,6 @@ module DiasporaFederation
15
18
  # @see PollAnswer#guid
16
19
  # @return [String] poll answer guid
17
20
  property :poll_answer_guid
18
-
19
- # The {PollParticipation} parent is a {Poll}
20
- # @return [String] parent type
21
- def parent_type
22
- "Poll"
23
- end
24
21
  end
25
22
  end
26
23
  end
@@ -55,6 +55,11 @@ module DiasporaFederation
55
55
 
56
56
  property :nsfw, default: false
57
57
  property :tag_string, default: nil
58
+
59
+ # @return [String] string representation of this object
60
+ def to_s
61
+ "Profile:#{author}"
62
+ end
58
63
  end
59
64
  end
60
65
  end
@@ -0,0 +1,33 @@
1
+ module DiasporaFederation
2
+ module Entities
3
+ # Entity meta informations for a related entity (parent or target of
4
+ # another entity).
5
+ class RelatedEntity < Entity
6
+ # @!attribute [r] author
7
+ # The diaspora ID of the author.
8
+ # @see Person#author
9
+ # @return [String] diaspora ID
10
+ property :author
11
+
12
+ # @!attribute [r] local
13
+ # +true+ if the owner of the entity is local on the pod
14
+ # @return [Boolean] is it a like or a dislike
15
+ property :local
16
+
17
+ # @!attribute [r] public
18
+ # shows whether the entity is visible to everyone or only to some aspects
19
+ # @return [Boolean] is it public
20
+ property :public, default: false
21
+
22
+ # @!attribute [r] parent
23
+ # if the entity also have a parent (Comment or Like), +nil+ if it has no parent
24
+ # @return [RelatedEntity] parent entity
25
+ entity :parent, Entities::RelatedEntity, default: nil
26
+
27
+ # never add {RelatedEntity} to xml
28
+ def to_xml
29
+ nil
30
+ end
31
+ end
32
+ end
33
+ end
@@ -45,20 +45,24 @@ module DiasporaFederation
45
45
  # This signature is required only when federation from upstream (parent) post author to
46
46
  # downstream subscribers. This is the case when the parent author has to resend a relayable
47
47
  # received from one of his subscribers to all others.
48
- #
49
48
  # @return [String] parent author signature
50
49
  #
51
- # @param [Entity] entity the entity in which it is included
52
- def self.included(entity)
53
- entity.class_eval do
50
+ # @!attribute [r] parent
51
+ # meta information about the parent object
52
+ # @return [RelatedEntity] parent entity
53
+ #
54
+ # @param [Entity] klass the entity in which it is included
55
+ def self.included(klass)
56
+ klass.class_eval do
54
57
  property :author, xml_name: :diaspora_handle
55
58
  property :guid
56
59
  property :parent_guid
57
60
  property :author_signature, default: nil
58
61
  property :parent_author_signature, default: nil
62
+ entity :parent, Entities::RelatedEntity
59
63
  end
60
64
 
61
- entity.extend ParseXML
65
+ klass.extend ParseXML
62
66
  end
63
67
 
64
68
  # Initializes a new relayable Entity with order and additional xml elements
@@ -77,62 +81,59 @@ module DiasporaFederation
77
81
  # verifies the signatures (+author_signature+ and +parent_author_signature+ if needed)
78
82
  # @raise [SignatureVerificationFailed] if the signature is not valid or no public key is found
79
83
  def verify_signatures
80
- pubkey = DiasporaFederation.callbacks.trigger(:fetch_public_key_by_diaspora_id, author)
81
- raise PublicKeyNotFound, "author_signature author=#{author} guid=#{guid}" if pubkey.nil?
82
- raise SignatureVerificationFailed, "wrong author_signature" unless verify_signature(pubkey, author_signature)
84
+ verify_signature(author, :author_signature)
83
85
 
84
- parent_author_local = DiasporaFederation.callbacks.trigger(:entity_author_is_local?, parent_type, parent_guid)
85
- verify_parent_author_signature unless parent_author_local
86
+ # this happens only on downstream federation
87
+ verify_signature(parent.author, :parent_author_signature) unless parent.local
86
88
  end
87
89
 
88
- private
89
-
90
- # this happens only on downstream federation
91
- def verify_parent_author_signature
92
- pubkey = DiasporaFederation.callbacks.trigger(:fetch_author_public_key_by_entity_guid, parent_type, parent_guid)
90
+ def sender_valid?(sender)
91
+ sender == author || sender == parent.author
92
+ end
93
93
 
94
- raise PublicKeyNotFound, "parent_author_signature parent_guid=#{parent_guid} guid=#{guid}" if pubkey.nil?
95
- unless verify_signature(pubkey, parent_author_signature)
96
- raise SignatureVerificationFailed, "wrong parent_author_signature parent_guid=#{parent_guid}"
97
- end
94
+ # @return [String] string representation of this object
95
+ def to_s
96
+ "#{super}#{":#{parent_type}" if respond_to?(:parent_type)}:#{parent_guid}"
98
97
  end
99
98
 
99
+ private
100
+
100
101
  # Check that signature is a correct signature
101
102
  #
102
- # @param [OpenSSL::PKey::RSA] pubkey An RSA key
103
- # @param [String] signature The signature to be verified.
103
+ # @param [String] author The author of the signature
104
+ # @param [String] signature_key The signature to be verified
104
105
  # @return [Boolean] signature valid
105
- def verify_signature(pubkey, signature)
106
- if signature.nil?
107
- logger.warn "event=verify_signature status=abort reason=no_signature guid=#{guid}"
108
- return false
109
- end
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?
110
109
 
111
- pubkey.verify(DIGEST, Base64.decode64(signature), signature_data).tap do |valid|
112
- logger.info "event=verify_signature status=complete guid=#{guid} valid=#{valid}"
113
- end
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
115
+
116
+ logger.info "event=verify_signature signature=#{signature_key} status=valid obj=#{self}"
114
117
  end
115
118
 
116
119
  # sign with author key
117
120
  # @raise [AuthorPrivateKeyNotFound] if the author private key is not found
118
121
  # @return [String] A Base64 encoded signature of #signature_data with key
119
122
  def sign_with_author
120
- privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key_by_diaspora_id, author)
121
- raise AuthorPrivateKeyNotFound, "author=#{author} guid=#{guid}" if privkey.nil?
123
+ privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key, author)
124
+ raise AuthorPrivateKeyNotFound, "author=#{author} obj=#{self}" if privkey.nil?
122
125
  sign_with_key(privkey).tap do
123
- logger.info "event=sign status=complete signature=author_signature author=#{author} guid=#{guid}"
126
+ logger.info "event=sign status=complete signature=author_signature author=#{author} obj=#{self}"
124
127
  end
125
128
  end
126
129
 
127
130
  # sign with parent author key, if the parent author is local (if the private key is found)
128
131
  # @return [String] A Base64 encoded signature of #signature_data with key
129
132
  def sign_with_parent_author_if_available
130
- privkey = DiasporaFederation.callbacks.trigger(
131
- :fetch_author_private_key_by_entity_guid, parent_type, parent_guid
132
- )
133
+ privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key, parent.author)
133
134
  if privkey
134
135
  sign_with_key(privkey).tap do
135
- logger.info "event=sign status=complete signature=parent_author_signature guid=#{guid}"
136
+ logger.info "event=sign status=complete signature=parent_author_signature obj=#{self}"
136
137
  end
137
138
  end
138
139
  end
@@ -152,7 +153,7 @@ module DiasporaFederation
152
153
  # @return [Hash] sorted xml elements with updated signatures
153
154
  def xml_elements
154
155
  xml_data = super.merge(additional_xml_elements)
155
- Hash[signature_order.map {|element| [element, xml_data[element]] }].tap do |xml_elements|
156
+ signature_order.map {|element| [element, xml_data[element]] }.to_h.tap do |xml_elements|
156
157
  xml_elements[:author_signature] = author_signature || sign_with_author
157
158
  xml_elements[:parent_author_signature] = parent_author_signature || sign_with_parent_author_if_available.to_s
158
159
  end
@@ -197,19 +198,33 @@ module DiasporaFederation
197
198
  end
198
199
  end
199
200
 
201
+ fetch_parent(entity_data)
200
202
  new(entity_data, xml_order, additional_xml_elements).tap(&:verify_signatures)
201
203
  end
204
+
205
+ def fetch_parent(data)
206
+ type = data[:parent_type] || self::PARENT_TYPE
207
+ guid = data[:parent_guid]
208
+
209
+ data[:parent] = DiasporaFederation.callbacks.trigger(:fetch_related_entity, type, guid)
210
+
211
+ unless data[:parent]
212
+ # fetch and receive parent from remote, if not available locally
213
+ Federation::Fetcher.fetch_public(data[:author], type, guid)
214
+ data[:parent] = DiasporaFederation.callbacks.trigger(:fetch_related_entity, type, guid)
215
+ end
216
+ end
202
217
  end
203
218
 
204
- # Exception raised when creating the author_signature failes, because the private key was not found
219
+ # Raised, if creating the author_signature failes, because the private key was not found
205
220
  class AuthorPrivateKeyNotFound < RuntimeError
206
221
  end
207
222
 
208
- # Exception raised when verify_signatures fails to verify signatures (no public key found)
223
+ # Raised, if verify_signatures fails to verify signatures (no public key found)
209
224
  class PublicKeyNotFound < RuntimeError
210
225
  end
211
226
 
212
- # Exception raised when verify_signatures fails to verify signatures (signatures are wrong)
227
+ # Raised, if verify_signatures fails to verify signatures (signatures are wrong)
213
228
  class SignatureVerificationFailed < RuntimeError
214
229
  end
215
230
  end
@@ -53,42 +53,51 @@ module DiasporaFederation
53
53
  # @return [String] target author signature
54
54
  property :target_author_signature, default: nil
55
55
 
56
+ # @!attribute [r] target
57
+ # target entity
58
+ # @return [RelatedEntity] target entity
59
+ entity :target, Entities::RelatedEntity
60
+
56
61
  # use only {Retraction} for receive
57
62
  # @return [Retraction] instance as normal retraction
58
63
  def to_retraction
59
- Retraction.new(author: author, target_guid: target_guid, target_type: target_type)
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}"
60
70
  end
61
71
 
62
72
  private
63
73
 
64
74
  # @param [Nokogiri::XML::Element] root_node xml nodes
65
75
  # @return [Retraction] instance
66
- def self.populate_entity(root_node)
67
- super(root_node).to_retraction
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
68
80
  end
69
- private_class_method :populate_entity
70
81
 
71
82
  # It updates also the signatures with the keys of the author and the parent
72
83
  # if the signatures are not there yet and if the keys are available.
73
84
  #
74
85
  # @return [Hash] xml elements with updated signatures
75
86
  def xml_elements
76
- target_author = DiasporaFederation.callbacks.trigger(:fetch_entity_author_id_by_guid, target_type, target_guid)
77
- privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key_by_diaspora_id, author)
87
+ privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key, author)
78
88
 
79
89
  super.tap do |xml_elements|
80
- fill_required_signature(target_author, privkey, xml_elements) unless privkey.nil?
90
+ fill_required_signature(privkey, xml_elements) unless privkey.nil?
81
91
  end
82
92
  end
83
93
 
84
- # @param [String] target_author the author of the entity to retract
85
94
  # @param [OpenSSL::PKey::RSA] privkey private key of sender
86
95
  # @param [Hash] hash hash given for a signing
87
- def fill_required_signature(target_author, privkey, hash)
88
- if target_author == author && target_author_signature.nil?
89
- hash[:target_author_signature] = SignedRetraction.sign_with_key(privkey, self)
90
- elsif target_author != author && parent_author_signature.nil?
96
+ def fill_required_signature(privkey, hash)
97
+ if target.parent.author == author && parent_author_signature.nil?
91
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)
92
101
  end
93
102
  end
94
103
  end
@@ -24,12 +24,16 @@ module DiasporaFederation
24
24
  Contact.new(author: author, recipient: recipient)
25
25
  end
26
26
 
27
+ # @return [String] string representation of this object
28
+ def to_s
29
+ "Request:#{author}:#{recipient}"
30
+ end
31
+
27
32
  # @param [Nokogiri::XML::Element] root_node xml nodes
28
33
  # @return [Retraction] instance
29
- def self.populate_entity(root_node)
34
+ private_class_method def self.populate_entity(root_node)
30
35
  super(root_node).to_contact
31
36
  end
32
- private_class_method :populate_entity
33
37
  end
34
38
  end
35
39
  end
@@ -22,6 +22,11 @@ module DiasporaFederation
22
22
  # has no meaning at the moment
23
23
  # @return [Boolean] public
24
24
  property :public, default: true # always true? (we only reshare public posts)
25
+
26
+ # @return [String] string representation of this object
27
+ def to_s
28
+ "#{super}:#{root_guid}"
29
+ end
25
30
  end
26
31
  end
27
32
  end
@@ -19,6 +19,43 @@ module DiasporaFederation
19
19
  # A string describing the type of the target.
20
20
  # @return [String] target type
21
21
  property :target_type, xml_name: :type
22
+
23
+ # @!attribute [r] target
24
+ # target entity
25
+ # @return [RelatedEntity] target entity
26
+ entity :target, Entities::RelatedEntity
27
+
28
+ def sender_valid?(sender)
29
+ case target_type
30
+ when "Comment", "Like", "PollParticipation"
31
+ sender == target.author || sender == target.parent.author
32
+ else
33
+ sender == target.author
34
+ end
35
+ end
36
+
37
+ # @return [String] string representation of this object
38
+ def to_s
39
+ "Retraction:#{target_type}:#{target_guid}"
40
+ end
41
+
42
+ # @param [Nokogiri::XML::Element] root_node xml nodes
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)
48
+ end
49
+
50
+ private_class_method def self.fetch_target(target_type, target_guid)
51
+ DiasporaFederation.callbacks.trigger(:fetch_related_entity, target_type, target_guid).tap do |target|
52
+ raise TargetNotFound, "not found: #{target_type}:#{target_guid}" unless target
53
+ end
54
+ end
55
+
56
+ # Raised, if the target of the {Retraction} was not found.
57
+ class TargetNotFound < RuntimeError
58
+ end
22
59
  end
23
60
  end
24
61
  end