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,29 @@
1
+ module DiasporaFederation
2
+ module Entities
3
+ # this entity represents a contact with another person. A user issues it
4
+ # when he starts sharing/following with another user.
5
+ #
6
+ # @see Validators::ContactValidator
7
+ class Contact < Entity
8
+ # @!attribute [r] author
9
+ # The diaspora ID of the person who shares his profile
10
+ # @see Person#author
11
+ # @return [String] sender ID
12
+ property :author
13
+
14
+ # @!attribute [r] recipient
15
+ # The diaspora ID of the person who will be shared with
16
+ # @see Validation::Rule::DiasporaId
17
+ # @return [String] recipient ID
18
+ property :recipient
19
+
20
+ # @!attribute [r] following
21
+ # @return [Boolean] if the author is following the person
22
+ property :following, default: true
23
+
24
+ # @!attribute [r] sharing
25
+ # @return [Boolean] if the author is sharing with the person
26
+ property :sharing, default: true
27
+ end
28
+ end
29
+ end
@@ -22,17 +22,16 @@ module DiasporaFederation
22
22
  # @return [[Entities::Message]] Messages of this conversation
23
23
  entity :messages, [Entities::Message]
24
24
 
25
- # @!attribute [r] diaspora_id
25
+ # @!attribute [r] author
26
26
  # The diaspora ID of the person initiated the conversation.
27
- # @see Person#diaspora_id
27
+ # @see Person#author
28
28
  # @return [String] diaspora ID
29
- property :diaspora_id, xml_name: :diaspora_handle
29
+ property :author, xml_name: :diaspora_handle
30
30
 
31
- # @!attribute [r] participant_ids
31
+ # @!attribute [r] participants
32
32
  # The diaspora IDs of the persons participating the conversation separated by ";".
33
- # @see Person#diaspora_id
34
33
  # @return [String] participants diaspora IDs
35
- property :participant_ids, xml_name: :participant_handles
34
+ property :participants, xml_name: :participant_handles
36
35
  end
37
36
  end
38
37
  end
@@ -4,32 +4,24 @@ module DiasporaFederation
4
4
  #
5
5
  # @see Validators::LikeValidator
6
6
  class Like < Entity
7
+ # old signature order
8
+ # @deprecated
9
+ LEGACY_SIGNATURE_ORDER = %i(positive guid parent_type parent_guid author).freeze
10
+
11
+ include Relayable
12
+
7
13
  # @!attribute [r] positive
8
14
  # If +true+ set a like, if +false+, set a dislike (dislikes are currently not
9
15
  # implemented in the Diaspora frontend).
10
16
  # @return [Boolean] is it a like or a dislike
11
17
  property :positive
12
18
 
13
- # @!attribute [r] guid
14
- # a random string of at least 16 chars.
15
- # @see Validation::Rule::Guid
16
- # @return [String] like guid
17
- property :guid
18
-
19
- # @!attribute [r] target_type
20
- # A string describing the type of the target.
19
+ # @!attribute [r] parent_type
20
+ # A string describing the type of the parent.
21
21
  # Can be "Post" or "Comment" (Comments are currently not implemented in the
22
22
  # Diaspora Frontend).
23
- # @return [String] target type
24
- property :target_type
25
-
26
- include Relayable
27
-
28
- # @!attribute [r] diaspora_id
29
- # The diaspora ID of the person who posts a like
30
- # @see Person#diaspora_id
31
- # @return [String] diaspora ID
32
- property :diaspora_id, xml_name: :diaspora_handle
23
+ # @return [String] parent type
24
+ property :parent_type, xml_name: :target_type
33
25
  end
34
26
  end
35
27
  end
@@ -4,11 +4,9 @@ module DiasporaFederation
4
4
  #
5
5
  # @see Validators::MessageValidator
6
6
  class Message < Entity
7
- # @!attribute [r] guid
8
- # a random string of at least 16 chars.
9
- # @see Validation::Rule::Guid
10
- # @return [String] message guid
11
- property :guid
7
+ # old signature order
8
+ # @deprecated
9
+ LEGACY_SIGNATURE_ORDER = %i(guid parent_guid text created_at author conversation_guid).freeze
12
10
 
13
11
  include Relayable
14
12
 
@@ -22,19 +20,15 @@ module DiasporaFederation
22
20
  # @return [Time] creation time
23
21
  property :created_at, default: -> { Time.now.utc }
24
22
 
25
- # @!attribute [r] diaspora_id
26
- # The diaspora ID of the message author.
27
- # @see Person#diaspora_id
28
- # @return [String] diaspora ID
29
- property :diaspora_id, xml_name: :diaspora_handle
30
-
31
23
  # @!attribute [r] conversation_guid
32
24
  # guid of a conversation this message belongs to
33
25
  # @see Conversation#guid
34
26
  # @return [String] conversation guid
35
27
  property :conversation_guid
36
28
 
37
- def self.get_target_entity_type(*)
29
+ # The {Message} parent is a {Conversation}
30
+ # @return [String] parent type
31
+ def parent_type
38
32
  "Conversation"
39
33
  end
40
34
  end
@@ -4,25 +4,17 @@ module DiasporaFederation
4
4
  #
5
5
  # @see Validators::Participation
6
6
  class Participation < Entity
7
- # @!attribute [r] guid
8
- # a random string of at least 16 chars.
9
- # @see Validation::Rule::Guid
10
- # @return [String] participation guid
11
- property :guid
12
-
13
- # @!attribute [r] target_type
14
- # a string describing a type of the target to subscribe on.
15
- # currently only "Post" is supported.
16
- # @return [String] target type
17
- property :target_type
7
+ # old signature order
8
+ # @deprecated
9
+ LEGACY_SIGNATURE_ORDER = %i(guid parent_type parent_guid author).freeze
18
10
 
19
11
  include Relayable
20
12
 
21
- # @!attribute [r] diaspora_id
22
- # The diaspora ID of the person who subscribes on a post
23
- # @see Person#diaspora_id
24
- # @return [String] diaspora ID
25
- property :diaspora_id, xml_name: :diaspora_handle
13
+ # @!attribute [r] parent_type
14
+ # a string describing a type of the target to subscribe on.
15
+ # currently only "Post" is supported.
16
+ # @return [String] parent type
17
+ property :parent_type, xml_name: :target_type
26
18
  end
27
19
  end
28
20
  end
@@ -11,11 +11,15 @@ module DiasporaFederation
11
11
  # @return [String] guid
12
12
  property :guid
13
13
 
14
- # @!attribute [r] diaspora_id
14
+ # @!attribute [r] author
15
15
  # The diaspora ID of the person
16
16
  # @see Validation::Rule::DiasporaId
17
17
  # @return [String] diaspora ID
18
- property :diaspora_id, xml_name: :diaspora_handle
18
+ # @!attribute [r] diaspora_id
19
+ # Alias for author
20
+ # @see Person#author
21
+ # @return [String] diaspora ID
22
+ property :author, alias: :diaspora_id, xml_name: :diaspora_handle
19
23
 
20
24
  # @!attribute [r] url
21
25
  # @see Discovery::WebFinger#seed_url
@@ -10,11 +10,11 @@ module DiasporaFederation
10
10
  # @return [String] guid
11
11
  property :guid
12
12
 
13
- # @!attribute [r] diaspora_id
13
+ # @!attribute [r] author
14
14
  # The diaspora ID of the person who uploaded the photo
15
- # @see Person#diaspora_id
15
+ # @see Person#author
16
16
  # @return [String] author diaspora ID
17
- property :diaspora_id, xml_name: :diaspora_handle
17
+ property :author, xml_name: :diaspora_handle
18
18
 
19
19
  # @!attribute [r] public
20
20
  # Points if the photo is visible to everyone or only to some aspects
@@ -4,27 +4,21 @@ module DiasporaFederation
4
4
  #
5
5
  # @see Validators::PollParticipationValidator
6
6
  class PollParticipation < Entity
7
- # @!attribute [r] guid
8
- # a random string of at least 16 chars.
9
- # @see Validation::Rule::Guid
10
- # @return [String] guid
11
- property :guid
7
+ # old signature order
8
+ # @deprecated
9
+ LEGACY_SIGNATURE_ORDER = %i(guid parent_guid author poll_answer_guid).freeze
12
10
 
13
11
  include Relayable
14
12
 
15
- # @!attribute [r] diaspora_id
16
- # The diaspora ID of the person who voted in the poll
17
- # @see Person#diaspora_id
18
- # @return [String] diaspora ID
19
- property :diaspora_id, xml_name: :diaspora_handle
20
-
21
13
  # @!attribute [r] poll_answer_guid
22
14
  # guid of the answer selected by the user.
23
15
  # @see PollAnswer#guid
24
16
  # @return [String] poll answer guid
25
17
  property :poll_answer_guid
26
18
 
27
- def self.get_target_entity_type(*)
19
+ # The {PollParticipation} parent is a {Poll}
20
+ # @return [String] parent type
21
+ def parent_type
28
22
  "Poll"
29
23
  end
30
24
  end
@@ -0,0 +1,37 @@
1
+ module DiasporaFederation
2
+ module Entities
3
+ # this is a module that defines common properties for a post which
4
+ # include {StatusMessage} and {Reshare}.
5
+ module Post
6
+ # on inclusion of this module the required properties for a post are added to the object that includes it
7
+ #
8
+ # @!attribute [r] author
9
+ # The diaspora ID of the person who posts the post
10
+ # @see Person#author
11
+ # @return [String] diaspora ID
12
+ #
13
+ # @!attribute [r] guid
14
+ # a random string of at least 16 chars.
15
+ # @see Validation::Rule::Guid
16
+ # @return [String] status message guid
17
+ #
18
+ # @!attribute [r] created_at
19
+ # post entity creation time
20
+ # @return [Time] creation time
21
+ #
22
+ # @!attribute [r] provider_display_name
23
+ # a string that describes a means by which a user has posted the post
24
+ # @return [String] provider display name
25
+ #
26
+ # @param [Entity] entity the entity in which it is included
27
+ def self.included(entity)
28
+ entity.class_eval do
29
+ property :author, xml_name: :diaspora_handle
30
+ property :guid
31
+ property :created_at, default: -> { Time.now.utc }
32
+ property :provider_display_name, default: nil
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -4,11 +4,15 @@ module DiasporaFederation
4
4
  #
5
5
  # @see Validators::ProfileValidator
6
6
  class Profile < Entity
7
- # @!attribute [r] diaspora_id
7
+ # @!attribute [r] author
8
8
  # The diaspora ID of the person
9
- # @see Person#diaspora_id
9
+ # @see Person#author
10
+ # @return [String] diaspora ID
11
+ # @!attribute [r] diaspora_id
12
+ # Alias for author
13
+ # @see Profile#author
10
14
  # @return [String] diaspora ID
11
- property :diaspora_id, xml_name: :diaspora_handle
15
+ property :author, alias: :diaspora_id, xml_name: :diaspora_handle
12
16
 
13
17
  # @!attribute [r] first_name
14
18
  # @deprecated We decided to only use one name field, these should be removed
@@ -2,15 +2,44 @@ module DiasporaFederation
2
2
  module Entities
3
3
  # this is a module that defines common properties for relayable entities
4
4
  # which include Like, Comment, Participation, Message, etc. Each relayable
5
- # has a parent, identified by guid. Relayables also are signed and signing/verificating
5
+ # has a parent, identified by guid. Relayables also are signed and signing/verification
6
6
  # logic is embedded into Salmon XML processing code.
7
7
  module Relayable
8
+ include Logging
9
+
10
+ # digest instance used for signing
11
+ DIGEST = OpenSSL::Digest::SHA256.new
12
+
13
+ # order from the parsed xml for signature
14
+ # @return [Array] order from xml
15
+ attr_reader :xml_order
16
+
17
+ # additional properties from parsed xml
18
+ # @return [Hash] additional xml elements
19
+ attr_reader :additional_xml_elements
20
+
8
21
  # on inclusion of this module the required properties for a relayable are added to the object that includes it
9
22
  #
23
+ # @!attribute [r] author
24
+ # The diaspora ID of the author.
25
+ # @see Person#author
26
+ # @return [String] diaspora ID
27
+ #
28
+ # @!attribute [r] guid
29
+ # a random string of at least 16 chars.
30
+ # @see Validation::Rule::Guid
31
+ # @return [String] comment guid
32
+ #
10
33
  # @!attribute [r] parent_guid
11
34
  # @see StatusMessage#guid
12
35
  # @return [String] parent guid
13
36
  #
37
+ # @!attribute [r] author_signature
38
+ # Contains a signature of the entity using the private key of the author of a post itself.
39
+ # The presence of this signature is mandatory. Without it the entity won't be accepted by
40
+ # a target pod.
41
+ # @return [String] author signature
42
+ #
14
43
  # @!attribute [r] parent_author_signature
15
44
  # Contains a signature of the entity using the private key of the author of a parent post
16
45
  # This signature is required only when federation from upstream (parent) post author to
@@ -19,95 +48,170 @@ module DiasporaFederation
19
48
  #
20
49
  # @return [String] parent author signature
21
50
  #
22
- # @!attribute [r] author_signature
23
- # Contains a signature of the entity using the private key of the author of a post itself.
24
- # The presence of this signature is mandatory. Without it the entity won't be accepted by
25
- # a target pod.
26
- # @return [String] author signature
27
- #
28
51
  # @param [Entity] entity the entity in which it is included
29
52
  def self.included(entity)
30
53
  entity.class_eval do
54
+ property :author, xml_name: :diaspora_handle
55
+ property :guid
31
56
  property :parent_guid
32
- property :parent_author_signature, default: nil
33
57
  property :author_signature, default: nil
34
-
35
- def self.get_target_entity_type(data)
36
- data[:target_type] || "Post"
37
- end
58
+ property :parent_author_signature, default: nil
38
59
  end
39
- end
40
-
41
- # Generates XML and updates signatures
42
- # @see Entity#to_xml
43
- # @return [Nokogiri::XML::Element] root element containing properties as child elements
44
- def to_xml
45
- entity_xml.tap do |xml|
46
- hash = to_h
47
- Relayable.update_signatures!(hash, self.class)
48
60
 
49
- xml.at_xpath("author_signature").content = hash[:author_signature]
50
- xml.at_xpath("parent_author_signature").content = hash[:parent_author_signature]
51
- end
61
+ entity.extend ParseXML
52
62
  end
53
63
 
54
- # Exception raised when verify_signatures fails to verify signatures (signatures are wrong)
55
- class SignatureVerificationFailed < ArgumentError
64
+ # Initializes a new relayable Entity with order and additional xml elements
65
+ #
66
+ # @param [Hash] data entity data
67
+ # @param [Array] xml_order order from xml
68
+ # @param [Hash] additional_xml_elements additional xml elements
69
+ # @see DiasporaFederation::Entity#initialize
70
+ def initialize(data, xml_order=nil, additional_xml_elements={})
71
+ @xml_order = xml_order
72
+ @additional_xml_elements = additional_xml_elements
73
+
74
+ super(data)
56
75
  end
57
76
 
58
77
  # verifies the signatures (+author_signature+ and +parent_author_signature+ if needed)
59
- # @param [Hash] data hash with data to verify
60
78
  # @raise [SignatureVerificationFailed] if the signature is not valid or no public key is found
61
- def self.verify_signatures(data, klass)
62
- pkey = DiasporaFederation.callbacks.trigger(:fetch_public_key_by_diaspora_id, data[:diaspora_id])
63
- raise SignatureVerificationFailed, "failed to fetch public key for #{data[:diaspora_id]}" if pkey.nil?
64
- raise SignatureVerificationFailed, "wrong author_signature" unless Signing.verify_signature(
65
- data, data[:author_signature], pkey
66
- )
79
+ 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)
67
83
 
68
- author_is_local = DiasporaFederation.callbacks.trigger(
69
- :entity_author_is_local?,
70
- klass.get_target_entity_type(data),
71
- data[:parent_guid]
72
- )
73
- verify_parent_signature(data, klass) unless author_is_local
84
+ parent_author_local = DiasporaFederation.callbacks.trigger(:entity_author_is_local?, parent_type, parent_guid)
85
+ verify_parent_author_signature unless parent_author_local
74
86
  end
75
87
 
88
+ private
89
+
76
90
  # this happens only on downstream federation
77
- # @param [Hash] data hash with data to verify
78
- def self.verify_parent_signature(data, klass)
79
- pkey = DiasporaFederation.callbacks.trigger(
80
- :fetch_author_public_key_by_entity_guid,
81
- klass.get_target_entity_type(data),
82
- data[:parent_guid]
83
- )
84
- raise SignatureVerificationFailed,
85
- "failed to fetch public key for author of #{data[:parent_guid]}" if pkey.nil?
86
- raise SignatureVerificationFailed, "wrong parent_author_signature" unless Signing.verify_signature(
87
- data, data[:parent_author_signature], pkey
91
+ def verify_parent_author_signature
92
+ pubkey = DiasporaFederation.callbacks.trigger(:fetch_author_public_key_by_entity_guid, parent_type, parent_guid)
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
98
+ end
99
+
100
+ # Check that signature is a correct signature
101
+ #
102
+ # @param [OpenSSL::PKey::RSA] pubkey An RSA key
103
+ # @param [String] signature The signature to be verified.
104
+ # @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
110
+
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
114
+ end
115
+
116
+ # sign with author key
117
+ # @raise [AuthorPrivateKeyNotFound] if the author private key is not found
118
+ # @return [String] A Base64 encoded signature of #signature_data with key
119
+ 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?
122
+ sign_with_key(privkey).tap do
123
+ logger.info "event=sign status=complete signature=author_signature author=#{author} guid=#{guid}"
124
+ end
125
+ end
126
+
127
+ # sign with parent author key, if the parent author is local (if the private key is found)
128
+ # @return [String] A Base64 encoded signature of #signature_data with key
129
+ def sign_with_parent_author_if_available
130
+ privkey = DiasporaFederation.callbacks.trigger(
131
+ :fetch_author_private_key_by_entity_guid, parent_type, parent_guid
88
132
  )
133
+ if privkey
134
+ sign_with_key(privkey).tap do
135
+ logger.info "event=sign status=complete signature=parent_author_signature guid=#{guid}"
136
+ end
137
+ end
89
138
  end
90
- private_class_method :verify_parent_signature
91
139
 
92
- # Adds signatures to a given hash with the keys of the author and the parent
93
- # if the signatures are not in the hash yet and if the keys are available.
140
+ # Sign the data with the key
94
141
  #
95
- # @param [Hash] data hash given for a signing
96
- def self.update_signatures!(data, klass)
97
- if data[:author_signature].nil?
98
- pkey = DiasporaFederation.callbacks.trigger(:fetch_private_key_by_diaspora_id, data[:diaspora_id])
99
- data[:author_signature] = Signing.sign_with_key(data, pkey) unless pkey.nil?
142
+ # @param [OpenSSL::PKey::RSA] privkey An RSA key
143
+ # @return [String] A Base64 encoded signature of #signature_data with key
144
+ def sign_with_key(privkey)
145
+ Base64.strict_encode64(privkey.sign(DIGEST, signature_data))
146
+ end
147
+
148
+ # Sort all XML elements according to the order used for the signatures.
149
+ # It updates also the signatures with the keys of the author and the parent
150
+ # if the signatures are not there yet and if the keys are available.
151
+ #
152
+ # @return [Hash] sorted xml elements with updated signatures
153
+ def xml_elements
154
+ xml_data = super.merge(additional_xml_elements)
155
+ Hash[signature_order.map {|element| [element, xml_data[element]] }].tap do |xml_elements|
156
+ xml_elements[:author_signature] = author_signature || sign_with_author
157
+ xml_elements[:parent_author_signature] = parent_author_signature || sign_with_parent_author_if_available.to_s
100
158
  end
159
+ end
101
160
 
102
- if data[:parent_author_signature].nil?
103
- pkey = DiasporaFederation.callbacks.trigger(
104
- :fetch_author_private_key_by_entity_guid,
105
- klass.get_target_entity_type(data),
106
- data[:parent_guid]
107
- )
108
- data[:parent_author_signature] = Signing.sign_with_key(data, pkey) unless pkey.nil?
161
+ # the order for signing
162
+ # @return [Array]
163
+ def signature_order
164
+ xml_order.nil? ? self.class::LEGACY_SIGNATURE_ORDER : xml_order.reject {|name| name =~ /signature/ }
165
+ end
166
+
167
+ # @return [String] signature data string
168
+ def signature_data
169
+ data = to_h.merge(additional_xml_elements)
170
+ signature_order.map {|name| data[name] }.join(";")
171
+ end
172
+
173
+ # override class methods from {Entity} to parse the xml
174
+ module ParseXML
175
+ private
176
+
177
+ # @param [Nokogiri::XML::Element] root_node xml nodes
178
+ # @return [Entity] instance
179
+ def populate_entity(root_node)
180
+ # Use all known properties to build the Entity (entity_data). All additional xml elements
181
+ # are respected and attached to a hash as string (additional_xml_elements). It also remembers
182
+ # the order of the xml-nodes (xml_order). This is needed to support receiving objects from
183
+ # the future versions of Diaspora, where new elements may have been added.
184
+ entity_data = {}
185
+ additional_xml_elements = {}
186
+
187
+ xml_order = root_node.element_children.map do |child|
188
+ xml_name = child.name
189
+ property = find_property_for_xml_name(xml_name)
190
+
191
+ if property
192
+ entity_data[property] = parse_element_from_node(xml_name, class_props[property], root_node)
193
+ property
194
+ else
195
+ additional_xml_elements[xml_name] = child.text
196
+ xml_name
197
+ end
198
+ end
199
+
200
+ new(entity_data, xml_order, additional_xml_elements).tap(&:verify_signatures)
109
201
  end
110
202
  end
203
+
204
+ # Exception raised when creating the author_signature failes, because the private key was not found
205
+ class AuthorPrivateKeyNotFound < RuntimeError
206
+ end
207
+
208
+ # Exception raised when verify_signatures fails to verify signatures (no public key found)
209
+ class PublicKeyNotFound < RuntimeError
210
+ end
211
+
212
+ # Exception raised when verify_signatures fails to verify signatures (signatures are wrong)
213
+ class SignatureVerificationFailed < RuntimeError
214
+ end
111
215
  end
112
216
  end
113
217
  end