Class: Lotus::Notification

Inherits:
Object
  • Object
show all
Defined in:
lib/lotus/notification.rb

Overview

This represents a notification that can be sent to a server when you wish to send information to a server that has not yet subscribed to you. Since this implies a lack of trust, a notification adds a layer so that the recipiant can verify the message contents.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(activity, signature = nil, plaintext = nil) ⇒ Notification

Create an instance for a particular Lotus::Activity.



13
14
15
16
17
# File 'lib/lotus/notification.rb', line 13

def initialize activity, signature = nil, plaintext = nil
  @activity = activity
  @signature = signature
  @plaintext = plaintext
end

Instance Attribute Details

#activityObject (readonly)

Returns the value of attribute activity.



10
11
12
# File 'lib/lotus/notification.rb', line 10

def activity
  @activity
end

Class Method Details

.from_data(content, content_type) ⇒ Object

Will pull a Lotus::Activity from the given payload and MIME type.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/lotus/notification.rb', line 61

def self.from_data(content, content_type)
  case content_type
  when 'xml',
       'magic-envelope+xml',
       'application/xml',
       'application/text+xml',
       'application/magic-envelope+xml'
    self.from_xml content
  when 'json',
       'magic-envelope+json',
       'application/json',
       'application/text+json',
       'application/magic-envelope+json'
    self.from_json content
  end
end

.from_follow(user_author, followed_author) ⇒ Object

Creates an activity for following a particular Author.



20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/lotus/notification.rb', line 20

def self.from_follow(user_author, followed_author)
  activity = Lotus::Activity.new(
    :verb => :follow,
    :object => followed_author,
    :actor    => user_author,
    :title    => "Now following #{followed_author.name}",
    :content  => "Now following #{followed_author.name}",
    :content_type => "html"
  )

  self.new(activity)
end

.from_json(source) ⇒ Object

Will pull a Lotus::Activity from a magic envelope described by the JSON.



79
80
# File 'lib/lotus/notification.rb', line 79

def self.from_json(source)
end

.from_profile_update(user_author) ⇒ Object

Creates an activity for a profile update.



48
49
50
51
52
53
54
55
56
57
58
# File 'lib/lotus/notification.rb', line 48

def self.from_profile_update(user_author)
  activity = Lotus::Activity.new(
    :verb => "http://ostatus.org/schema/1.0/update-profile",
    :actor    => user_author,
    :title => "#{user_author.name} changed their profile information.",
    :content => "#{user_author.name} changed their profile information.",
    :content_type => "html"
  )

  self.new(activity)
end

.from_unfollow(user_author, followed_author) ⇒ Object

Creates an activity for unfollowing a particular Author.



34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/lotus/notification.rb', line 34

def self.from_unfollow(user_author, followed_author)
  activity = Lotus::Activity.new(
    :verb => "http://ostatus.org/schema/1.0/unfollow",
    :object => followed_author,
    :actor    => user_author,
    :title => "Stopped following #{followed_author.name}",
    :content => "Stopped following #{followed_author.name}",
    :content_type => "html"
  )

  self.new(activity)
end

.from_xml(source) ⇒ Object

Will pull a Lotus::Activity from a magic envelope described by the XML.



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/lotus/notification.rb', line 83

def self.from_xml(source)
  if source.is_a?(String)
    if source.length == 0
      return nil
    end

    source = XML::Document.string(source,
                                  :options => XML::Parser::Options::NOENT)
  else
    return nil
  end

  # Retrieve the envelope
  envelope = source.find('/me:env',
                         'me:http://salmon-protocol.org/ns/magic-env').first

  return nil unless envelope

  data = envelope.find('me:data',
                       'me:http://salmon-protocol.org/ns/magic-env').first
  return nil unless data

  data_type = data.attributes["type"]
  if data_type.nil?
    data_type = 'application/atom+xml'
    armored_data_type = ''
  else
    armored_data_type = Base64::urlsafe_encode64(data_type)
  end

  encoding = envelope.find('me:encoding',
                           'me:http://salmon-protocol.org/ns/magic-env').first

  algorithm = envelope.find(
                      'me:alg',
                      'me:http://salmon-protocol.org/ns/magic-env').first

  signature = source.find('me:sig',
                       'me:http://salmon-protocol.org/ns/magic-env').first

  # Parse fields

  # Well, if we cannot verify, we don't accept
  return nil unless signature

  # XXX: Handle key_id attribute
  signature = signature.content
  signature = Base64::urlsafe_decode64(signature)

  if encoding.nil?
    # When the encoding is omitted, use base64url
    # Cite: Magic Envelope Draft Spec Section 3.3
    armored_encoding = ''
    encoding = 'base64url'
  else
    armored_encoding = Base64::urlsafe_encode64(encoding.content)
    encoding = encoding.content.downcase
  end

  if algorithm.nil?
    # When algorithm is omitted, use 'RSA-SHA256'
    # Cite: Magic Envelope Draft Spec Section 3.3
    armored_algorithm = ''
    algorithm = 'rsa-sha256'
  else
    armored_algorithm = Base64::urlsafe_encode64(algorithm.content)
    algorithm = algorithm.content.downcase
  end

  # Retrieve and decode data payload

  data = data.content
  armored_data = data

  case encoding
  when 'base64url'
    data = Base64::urlsafe_decode64(data)
  else
    # Unsupported data encoding
    return nil
  end

  # Signature plaintext
  plaintext = "#{armored_data}.#{armored_data_type}.#{armored_encoding}.#{armored_algorithm}"

  # Interpret data payload
  payload = XML::Reader.string(data)
  self.new Lotus::Atom::Entry.new(payload).to_canonical, signature, plaintext
end

Instance Method Details

#to_xml(private_key) ⇒ Object

Generate the xml for this notice and sign with the given private key.



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/lotus/notification.rb', line 174

def to_xml private_key
  # Generate magic envelope
  magic_envelope = XML::Document.new

  magic_envelope.root = XML::Node.new 'env'

  me_ns = XML::Namespace.new(magic_envelope.root,
               'me', 'http://salmon-protocol.org/ns/magic-env')

  magic_envelope.root.namespaces.namespace = me_ns

  # Armored Data <me:data>
  data = @activity.to_atom
  @plaintext = data
  data_armored = Base64::urlsafe_encode64(data)
  elem = XML::Node.new 'data', data_armored, me_ns
  elem.attributes['type'] = 'application/atom+xml'
  data_type_armored = 'YXBwbGljYXRpb24vYXRvbSt4bWw='
  magic_envelope.root << elem

  # Encoding <me:encoding>
  magic_envelope.root << XML::Node.new('encoding', 'base64url', me_ns)
  encoding_armored = 'YmFzZTY0dXJs'

  # Signing Algorithm <me:alg>
  magic_envelope.root << XML::Node.new('alg', 'RSA-SHA256', me_ns)
  algorithm_armored = 'UlNBLVNIQTI1Ng=='

  # Signature <me:sig>
  plaintext =
    "#{data_armored}.#{data_type_armored}.#{encoding_armored}.#{algorithm_armored}"

  # Assign @signature to the signature generated from the plaintext
  @signature = Lotus::Crypto.emsa_sign(plaintext, private_key)

  signature_armored = Base64::urlsafe_encode64(@signature)
  magic_envelope.root << XML::Node.new('sig', signature_armored, me_ns)

  magic_envelope.to_s :indent => true, :encoding => XML::Encoding::UTF_8
end

#verified?(key) ⇒ Boolean

Check the origin of this notification.

Returns:

  • (Boolean)


216
217
218
# File 'lib/lotus/notification.rb', line 216

def verified? key
  Lotus::Crypto.emsa_verify(@plaintext, @signature, key)
end