Class: Nelumba::Notification

Inherits:
Object
  • Object
show all
Defined in:
lib/nelumba/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 Nelumba::Activity.



17
18
19
20
21
22
23
24
25
26
# File 'lib/nelumba/notification.rb', line 17

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

   = activity.actor.uri

  # XXX: Negotiate various weird uri schemes to find identity account
  @account = 
end

Instance Attribute Details

#accountObject (readonly)

The identity of the sender that can be used to discover the Identity



14
15
16
# File 'lib/nelumba/notification.rb', line 14

def 
  @account
end

#activityObject (readonly)

The Activity that is represented by this notification.



11
12
13
# File 'lib/nelumba/notification.rb', line 11

def activity
  @activity
end

Class Method Details

.from_data(content, content_type) ⇒ Object

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



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/nelumba/notification.rb', line 70

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 Person.



29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/nelumba/notification.rb', line 29

def self.from_follow(user_author, followed_author)
  activity = Nelumba::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 Nelumba::Activity from a magic envelope described by the JSON.



88
89
# File 'lib/nelumba/notification.rb', line 88

def self.from_json(source)
end

.from_profile_update(user_author) ⇒ Object

Creates an activity for a profile update.



57
58
59
60
61
62
63
64
65
66
67
# File 'lib/nelumba/notification.rb', line 57

def self.from_profile_update(user_author)
  activity = Nelumba::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 Person.



43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/nelumba/notification.rb', line 43

def self.from_unfollow(user_author, followed_author)
  activity = Nelumba::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 Nelumba::Activity from a magic envelope described by the XML.



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
172
173
174
175
176
177
178
179
180
# File 'lib/nelumba/notification.rb', line 92

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 Nelumba::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.



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
214
215
216
217
218
219
220
221
222
# File 'lib/nelumba/notification.rb', line 183

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 = Nelumba::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)


225
226
227
# File 'lib/nelumba/notification.rb', line 225

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