diaspora_federation 0.0.13 → 0.1.0

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