ruby-openid2 3.0.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 (59) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +136 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/CONTRIBUTING.md +54 -0
  6. data/LICENSE.txt +210 -0
  7. data/README.md +81 -0
  8. data/SECURITY.md +15 -0
  9. data/lib/hmac/hmac.rb +110 -0
  10. data/lib/hmac/sha1.rb +11 -0
  11. data/lib/hmac/sha2.rb +25 -0
  12. data/lib/openid/association.rb +246 -0
  13. data/lib/openid/consumer/associationmanager.rb +354 -0
  14. data/lib/openid/consumer/checkid_request.rb +179 -0
  15. data/lib/openid/consumer/discovery.rb +516 -0
  16. data/lib/openid/consumer/discovery_manager.rb +144 -0
  17. data/lib/openid/consumer/html_parse.rb +142 -0
  18. data/lib/openid/consumer/idres.rb +513 -0
  19. data/lib/openid/consumer/responses.rb +147 -0
  20. data/lib/openid/consumer/session.rb +36 -0
  21. data/lib/openid/consumer.rb +406 -0
  22. data/lib/openid/cryptutil.rb +112 -0
  23. data/lib/openid/dh.rb +84 -0
  24. data/lib/openid/extension.rb +38 -0
  25. data/lib/openid/extensions/ax.rb +552 -0
  26. data/lib/openid/extensions/oauth.rb +88 -0
  27. data/lib/openid/extensions/pape.rb +170 -0
  28. data/lib/openid/extensions/sreg.rb +268 -0
  29. data/lib/openid/extensions/ui.rb +49 -0
  30. data/lib/openid/fetchers.rb +277 -0
  31. data/lib/openid/kvform.rb +113 -0
  32. data/lib/openid/kvpost.rb +62 -0
  33. data/lib/openid/message.rb +555 -0
  34. data/lib/openid/protocolerror.rb +7 -0
  35. data/lib/openid/server.rb +1571 -0
  36. data/lib/openid/store/filesystem.rb +260 -0
  37. data/lib/openid/store/interface.rb +73 -0
  38. data/lib/openid/store/memcache.rb +109 -0
  39. data/lib/openid/store/memory.rb +79 -0
  40. data/lib/openid/store/nonce.rb +72 -0
  41. data/lib/openid/trustroot.rb +597 -0
  42. data/lib/openid/urinorm.rb +72 -0
  43. data/lib/openid/util.rb +119 -0
  44. data/lib/openid/version.rb +5 -0
  45. data/lib/openid/yadis/accept.rb +141 -0
  46. data/lib/openid/yadis/constants.rb +16 -0
  47. data/lib/openid/yadis/discovery.rb +151 -0
  48. data/lib/openid/yadis/filters.rb +192 -0
  49. data/lib/openid/yadis/htmltokenizer.rb +290 -0
  50. data/lib/openid/yadis/parsehtml.rb +50 -0
  51. data/lib/openid/yadis/services.rb +44 -0
  52. data/lib/openid/yadis/xrds.rb +160 -0
  53. data/lib/openid/yadis/xri.rb +86 -0
  54. data/lib/openid/yadis/xrires.rb +87 -0
  55. data/lib/openid.rb +27 -0
  56. data/lib/ruby-openid.rb +1 -0
  57. data.tar.gz.sig +0 -0
  58. metadata +331 -0
  59. metadata.gz.sig +0 -0
data/lib/openid/dh.rb ADDED
@@ -0,0 +1,84 @@
1
+ require_relative "util"
2
+ require_relative "cryptutil"
3
+
4
+ module OpenID
5
+ # Encapsulates a Diffie-Hellman key exchange. This class is used
6
+ # internally by both the consumer and server objects.
7
+ #
8
+ # Read more about Diffie-Hellman on wikipedia:
9
+ # http://en.wikipedia.org/wiki/Diffie-Hellman
10
+
11
+ class DiffieHellman
12
+ # From the OpenID specification
13
+ @@default_mod = 155_172_898_181_473_697_471_232_257_763_715_539_915_724_801_966_915_404_479_707_795_314_057_629_378_541_917_580_651_227_423_698_188_993_727_816_152_646_631_438_561_595_825_688_188_889_951_272_158_842_675_419_950_341_258_706_556_549_803_580_104_870_537_681_476_726_513_255_747_040_765_857_479_291_291_572_334_510_643_245_094_715_007_229_621_094_194_349_783_925_984_760_375_594_985_848_253_359_305_585_439_638_443
14
+ @@default_gen = 2
15
+
16
+ attr_reader :modulus, :generator, :public
17
+
18
+ # A new DiffieHellman object, using the modulus and generator from
19
+ # the OpenID specification
20
+ def self.from_defaults
21
+ DiffieHellman.new(@@default_mod, @@default_gen)
22
+ end
23
+
24
+ def initialize(modulus = nil, generator = nil, priv = nil)
25
+ @modulus = modulus.nil? ? @@default_mod : modulus
26
+ @generator = generator.nil? ? @@default_gen : generator
27
+ set_private(priv.nil? ? OpenID::CryptUtil.rand(@modulus - 2) + 1 : priv)
28
+ end
29
+
30
+ def get_shared_secret(composite)
31
+ DiffieHellman.powermod(composite, @private, @modulus)
32
+ end
33
+
34
+ def xor_secret(algorithm, composite, secret)
35
+ dh_shared = get_shared_secret(composite)
36
+ packed_dh_shared = OpenID::CryptUtil.num_to_binary(dh_shared)
37
+ hashed_dh_shared = algorithm.call(packed_dh_shared)
38
+ DiffieHellman.strxor(secret, hashed_dh_shared)
39
+ end
40
+
41
+ def using_default_values?
42
+ @generator == @@default_gen && @modulus == @@default_mod
43
+ end
44
+
45
+ private
46
+
47
+ def set_private(priv)
48
+ @private = priv
49
+ @public = DiffieHellman.powermod(@generator, @private, @modulus)
50
+ end
51
+
52
+ def self.strxor(s, t)
53
+ if s.length != t.length
54
+ raise ArgumentError, "strxor: lengths don't match. " +
55
+ "Inputs were #{s.inspect} and #{t.inspect}"
56
+ end
57
+
58
+ if String.method_defined?(:bytes)
59
+ s.bytes.to_a.zip(t.bytes.to_a).map { |sb, tb| sb ^ tb }.pack("C*")
60
+ else
61
+ indices = 0...(s.length)
62
+ chrs = indices.collect { |i| (s[i] ^ t[i]).chr }
63
+ chrs.join("")
64
+ end
65
+ end
66
+
67
+ # This code is taken from this post:
68
+ # <http://blade.nagaokaut.ac.jp/cgi-bin/scat.\rb/ruby/ruby-talk/19098>
69
+ # by Eric Lee Green.
70
+ def self.powermod(x, n, q)
71
+ counter = 0
72
+ n_p = n
73
+ y_p = 1
74
+ z_p = x
75
+ while n_p != 0
76
+ y_p = (y_p * z_p) % q if n_p[0] == 1
77
+ n_p >>= 1
78
+ z_p = (z_p * z_p) % q
79
+ counter += 1
80
+ end
81
+ y_p
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,38 @@
1
+ require_relative "message"
2
+
3
+ module OpenID
4
+ # An interface for OpenID extensions.
5
+ class Extension < Object
6
+ def initialize
7
+ @ns_uri = nil
8
+ @ns_alias = nil
9
+ end
10
+
11
+ # Get the string arguments that should be added to an OpenID
12
+ # message for this extension.
13
+ def get_extension_args
14
+ raise NotImplementedError
15
+ end
16
+
17
+ # Add the arguments from this extension to the provided
18
+ # message, or create a new message containing only those
19
+ # arguments. Returns the message with added extension args.
20
+ def to_message(message = nil)
21
+ if message.nil?
22
+ # warnings.warn('Passing None to Extension.toMessage is deprecated. '
23
+ # 'Creating a message assuming you want OpenID 2.',
24
+ # DeprecationWarning, stacklevel=2)
25
+ Message.new(OPENID2_NS)
26
+ end
27
+ message = Message.new if message.nil?
28
+
29
+ implicit = message.is_openid1
30
+
31
+ message.namespaces.add_alias(@ns_uri, @ns_alias, implicit)
32
+ # XXX python ignores keyerror if m.ns.getAlias(uri) == alias
33
+
34
+ message.update_args(@ns_uri, get_extension_args)
35
+ message
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,552 @@
1
+ # Implements the OpenID attribute exchange specification, version 1.0
2
+
3
+ require_relative "../extension"
4
+ require_relative "../trustroot"
5
+ require_relative "../message"
6
+
7
+ module OpenID
8
+ module AX
9
+ UNLIMITED_VALUES = "unlimited"
10
+ MINIMUM_SUPPORTED_ALIAS_LENGTH = 32
11
+
12
+ # check alias for invalid characters, raise AXError if found
13
+ def self.check_alias(name)
14
+ return unless /(,|\.)/.match?(name)
15
+
16
+ raise Error, "Alias #{name.inspect} must not contain a " \
17
+ "comma or period."
18
+ end
19
+
20
+ # Raised when data does not comply with AX 1.0 specification
21
+ class Error < ArgumentError
22
+ end
23
+
24
+ # Abstract class containing common code for attribute exchange messages
25
+ class AXMessage < Extension
26
+ attr_accessor :ns_alias, :mode, :ns_uri
27
+
28
+ NS_URI = "http://openid.net/srv/ax/1.0"
29
+
30
+ begin
31
+ Message.register_namespace_alias(NS_URI, "ax")
32
+ rescue NamespaceAliasRegistrationError => e
33
+ Util.log(e)
34
+ end
35
+
36
+ def initialize
37
+ @ns_alias = "ax"
38
+ @ns_uri = NS_URI
39
+ @mode = nil
40
+ end
41
+
42
+ protected
43
+
44
+ # Raise an exception if the mode in the attribute exchange
45
+ # arguments does not match what is expected for this class.
46
+ def check_mode(ax_args)
47
+ actual_mode = ax_args ? ax_args["mode"] : nil
48
+ return unless actual_mode != @mode
49
+
50
+ raise Error, "Expected mode #{mode.inspect}, got #{actual_mode.inspect}"
51
+ end
52
+
53
+ def new_args
54
+ {"mode" => @mode}
55
+ end
56
+ end
57
+
58
+ # Represents a single attribute in an attribute exchange
59
+ # request. This should be added to an Request object in order to
60
+ # request the attribute.
61
+ #
62
+ # @ivar required: Whether the attribute will be marked as required
63
+ # when presented to the subject of the attribute exchange
64
+ # request.
65
+ # @type required: bool
66
+ #
67
+ # @ivar count: How many values of this type to request from the
68
+ # subject. Defaults to one.
69
+ # @type count: int
70
+ #
71
+ # @ivar type_uri: The identifier that determines what the attribute
72
+ # represents and how it is serialized. For example, one type URI
73
+ # representing dates could represent a Unix timestamp in base 10
74
+ # and another could represent a human-readable string.
75
+ # @type type_uri: str
76
+ #
77
+ # @ivar ns_alias: The name that should be given to this alias in the
78
+ # request. If it is not supplied, a generic name will be
79
+ # assigned. For example, if you want to call a Unix timestamp
80
+ # value 'tstamp', set its alias to that value. If two attributes
81
+ # in the same message request to use the same alias, the request
82
+ # will fail to be generated.
83
+ # @type alias: str or NoneType
84
+ class AttrInfo < Object
85
+ attr_reader :type_uri, :count, :ns_alias
86
+ attr_accessor :required
87
+
88
+ def initialize(type_uri, ns_alias = nil, required = false, count = 1)
89
+ @type_uri = type_uri
90
+ @count = count
91
+ @required = required
92
+ @ns_alias = ns_alias
93
+ end
94
+
95
+ def wants_unlimited_values?
96
+ @count == UNLIMITED_VALUES
97
+ end
98
+ end
99
+
100
+ # Given a namespace mapping and a string containing a
101
+ # comma-separated list of namespace aliases, return a list of type
102
+ # URIs that correspond to those aliases.
103
+ # namespace_map: OpenID::NamespaceMap
104
+ def self.to_type_uris(namespace_map, alias_list_s)
105
+ return [] if alias_list_s.nil?
106
+
107
+ alias_list_s.split(",").inject([]) do |uris, name|
108
+ type_uri = namespace_map.get_namespace_uri(name)
109
+ raise IndexError, "No type defined for attribute name #{name.inspect}" if type_uri.nil?
110
+
111
+ uris << type_uri
112
+ end
113
+ end
114
+
115
+ # An attribute exchange 'fetch_request' message. This message is
116
+ # sent by a relying party when it wishes to obtain attributes about
117
+ # the subject of an OpenID authentication request.
118
+ class FetchRequest < AXMessage
119
+ attr_reader :requested_attributes
120
+ attr_accessor :update_url
121
+
122
+ MODE = "fetch_request"
123
+
124
+ def initialize(update_url = nil)
125
+ super()
126
+ @mode = MODE
127
+ @requested_attributes = {}
128
+ @update_url = update_url
129
+ end
130
+
131
+ # Add an attribute to this attribute exchange request.
132
+ # attribute: AttrInfo, the attribute being requested
133
+ # Raises IndexError if the requested attribute is already present
134
+ # in this request.
135
+ def add(attribute)
136
+ if @requested_attributes[attribute.type_uri]
137
+ raise IndexError, "The attribute #{attribute.type_uri} has already been requested"
138
+ end
139
+
140
+ @requested_attributes[attribute.type_uri] = attribute
141
+ end
142
+
143
+ # Get the serialized form of this attribute fetch request.
144
+ # returns a hash of the arguments
145
+ def get_extension_args
146
+ aliases = NamespaceMap.new
147
+ required = []
148
+ if_available = []
149
+ ax_args = new_args
150
+ @requested_attributes.each do |type_uri, attribute|
151
+ name = if attribute.ns_alias
152
+ aliases.add_alias(type_uri, attribute.ns_alias)
153
+ else
154
+ aliases.add(type_uri)
155
+ end
156
+ if attribute.required
157
+ required << name
158
+ else
159
+ if_available << name
160
+ end
161
+ ax_args["count.#{name}"] = attribute.count.to_s if attribute.count != 1
162
+ ax_args["type.#{name}"] = type_uri
163
+ end
164
+
165
+ ax_args["required"] = required.join(",") unless required.empty?
166
+ ax_args["if_available"] = if_available.join(",") unless if_available.empty?
167
+ ax_args
168
+ end
169
+
170
+ # Get the type URIs for all attributes that have been marked
171
+ # as required.
172
+ def get_required_attrs
173
+ @requested_attributes.inject([]) do |required, (type_uri, attribute)|
174
+ if attribute.required
175
+ required << type_uri
176
+ else
177
+ required
178
+ end
179
+ end
180
+ end
181
+
182
+ # Extract a FetchRequest from an OpenID message
183
+ # message: OpenID::Message
184
+ # return a FetchRequest or nil if AX arguments are not present
185
+ def self.from_openid_request(oidreq)
186
+ message = oidreq.message
187
+ ax_args = message.get_args(NS_URI)
188
+ return if ax_args == {} or ax_args["mode"] != MODE
189
+
190
+ req = new
191
+ req.parse_extension_args(ax_args)
192
+
193
+ if req.update_url
194
+ realm = message.get_arg(
195
+ OPENID_NS,
196
+ "realm",
197
+ message.get_arg(OPENID_NS, "return_to"),
198
+ )
199
+ if realm.nil? or realm.empty?
200
+ raise Error, "Cannot validate update_url #{req.update_url.inspect} against absent realm"
201
+ end
202
+
203
+ tr = TrustRoot::TrustRoot.parse(realm)
204
+ unless tr.validate_url(req.update_url)
205
+ raise Error, "Update URL #{req.update_url.inspect} failed validation against realm #{realm.inspect}"
206
+ end
207
+ end
208
+
209
+ req
210
+ end
211
+
212
+ def parse_extension_args(ax_args)
213
+ check_mode(ax_args)
214
+
215
+ aliases = NamespaceMap.new
216
+
217
+ ax_args.each do |k, v|
218
+ next unless k.index("type.") == 0
219
+
220
+ name = k[5..-1]
221
+ type_uri = v
222
+ aliases.add_alias(type_uri, name)
223
+
224
+ count_key = "count." + name
225
+ count_s = ax_args[count_key]
226
+ count = 1
227
+ if count_s
228
+ if count_s == UNLIMITED_VALUES
229
+ count = count_s
230
+ else
231
+ count = count_s.to_i
232
+ raise Error, "Invalid value for count #{count_key.inspect}: #{count_s.inspect}" if count <= 0
233
+ end
234
+ end
235
+ add(AttrInfo.new(type_uri, name, false, count))
236
+ end
237
+
238
+ required = AX.to_type_uris(aliases, ax_args["required"])
239
+ required.each do |type_uri|
240
+ @requested_attributes[type_uri].required = true
241
+ end
242
+ if_available = AX.to_type_uris(aliases, ax_args["if_available"])
243
+ all_type_uris = required + if_available
244
+
245
+ aliases.namespace_uris.each do |type_uri|
246
+ unless all_type_uris.member?(type_uri)
247
+ raise Error,
248
+ "Type URI #{type_uri.inspect} was in the request but not present in 'required' or 'if_available'"
249
+ end
250
+ end
251
+ @update_url = ax_args["update_url"]
252
+ end
253
+
254
+ # return the list of AttrInfo objects contained in the FetchRequest
255
+ def attributes
256
+ @requested_attributes.values
257
+ end
258
+
259
+ # return the list of requested attribute type URIs
260
+ def requested_types
261
+ @requested_attributes.keys
262
+ end
263
+
264
+ def member?(type_uri)
265
+ !@requested_attributes[type_uri].nil?
266
+ end
267
+ end
268
+
269
+ # Abstract class that implements a message that has attribute
270
+ # keys and values. It contains the common code between
271
+ # fetch_response and store_request.
272
+ class KeyValueMessage < AXMessage
273
+ attr_reader :data
274
+
275
+ def initialize
276
+ super
277
+ @mode = nil
278
+ @data = Hash.new { |hash, key| hash[key] = [] }
279
+ end
280
+
281
+ # Add a single value for the given attribute type to the
282
+ # message. If there are already values specified for this type,
283
+ # this value will be sent in addition to the values already
284
+ # specified.
285
+ def add_value(type_uri, value)
286
+ @data[type_uri] = @data[type_uri] << value
287
+ end
288
+
289
+ # Set the values for the given attribute type. This replaces
290
+ # any values that have already been set for this attribute.
291
+ def set_values(type_uri, values)
292
+ @data[type_uri] = values
293
+ end
294
+
295
+ # Get the extension arguments for the key/value pairs
296
+ # contained in this message.
297
+ def _get_extension_kv_args(aliases = nil)
298
+ aliases = NamespaceMap.new if aliases.nil?
299
+
300
+ ax_args = new_args
301
+
302
+ @data.each do |type_uri, values|
303
+ name = aliases.add(type_uri)
304
+ ax_args["type." + name] = type_uri
305
+ if values.size > 1
306
+ ax_args["count." + name] = values.size.to_s
307
+
308
+ values.each_with_index do |value, i|
309
+ key = "value.#{name}.#{i + 1}"
310
+ ax_args[key] = value
311
+ end
312
+ # for attributes with only a single value, use a
313
+ # nice shortcut to only show the value w/o the count
314
+ else
315
+ values.each do |value|
316
+ key = "value.#{name}"
317
+ ax_args[key] = value
318
+ end
319
+ end
320
+ end
321
+ ax_args
322
+ end
323
+
324
+ # Parse attribute exchange key/value arguments into this object.
325
+
326
+ def parse_extension_args(ax_args)
327
+ check_mode(ax_args)
328
+ aliases = NamespaceMap.new
329
+
330
+ ax_args.each do |k, v|
331
+ next unless k.index("type.") == 0
332
+
333
+ type_uri = v
334
+ name = k[5..-1]
335
+
336
+ AX.check_alias(name)
337
+ aliases.add_alias(type_uri, name)
338
+ end
339
+
340
+ aliases.each do |type_uri, name|
341
+ count_s = ax_args["count." + name]
342
+ count = count_s.to_i
343
+ if count_s.nil?
344
+ value = ax_args["value." + name]
345
+ if value.nil?
346
+ raise IndexError, "Missing #{"value." + name} in FetchResponse"
347
+ elsif value.empty?
348
+ values = []
349
+ else
350
+ values = [value]
351
+ end
352
+ elsif count_s.to_i == 0
353
+ values = []
354
+ else
355
+ values = (1..count).inject([]) do |l, i|
356
+ key = "value.#{name}.#{i}"
357
+ v = ax_args[key]
358
+ raise IndexError, "Missing #{key} in FetchResponse" if v.nil?
359
+
360
+ l << v
361
+ end
362
+ end
363
+ @data[type_uri] = values
364
+ end
365
+ end
366
+
367
+ # Get a single value for an attribute. If no value was sent
368
+ # for this attribute, use the supplied default. If there is more
369
+ # than one value for this attribute, this method will fail.
370
+ def get_single(type_uri, default = nil)
371
+ values = @data[type_uri]
372
+ return default if values.empty?
373
+ raise Error, "More than one value present for #{type_uri.inspect}" if values.size != 1
374
+
375
+ values[0]
376
+ end
377
+
378
+ # retrieve the list of values for this attribute
379
+ def get(type_uri)
380
+ @data[type_uri]
381
+ end
382
+
383
+ # retrieve the list of values for this attribute
384
+ def [](type_uri)
385
+ @data[type_uri]
386
+ end
387
+
388
+ # get the number of responses for this attribute
389
+ def count(type_uri)
390
+ @data[type_uri].size
391
+ end
392
+ end
393
+
394
+ # A fetch_response attribute exchange message
395
+ class FetchResponse < KeyValueMessage
396
+ attr_reader :update_url
397
+ # Use the aliases variable to manually add alias names in the response.
398
+ # They'll be returned to the client in the format:
399
+ # openid.ax.type.email=http://openid.net/schema/contact/internet/email
400
+ # openid.ax.value.email=guy@example.com
401
+ attr_accessor :aliases
402
+
403
+ def initialize(update_url = nil)
404
+ super()
405
+ @mode = "fetch_response"
406
+ @update_url = update_url
407
+ @aliases = NamespaceMap.new
408
+ end
409
+
410
+ # Serialize this object into arguments in the attribute
411
+ # exchange namespace
412
+ # Takes an optional FetchRequest. If specified, the response will be
413
+ # validated against this request, and empty responses for requested
414
+ # fields with no data will be sent.
415
+ def get_extension_args(request = nil)
416
+ zero_value_types = []
417
+
418
+ if request
419
+ # Validate the data in the context of the request (the
420
+ # same attributes should be present in each, and the
421
+ # counts in the response must be no more than the counts
422
+ # in the request)
423
+ @data.keys.each do |type_uri|
424
+ unless request.member?(type_uri)
425
+ raise IndexError, "Response attribute not present in request: #{type_uri.inspect}"
426
+ end
427
+ end
428
+
429
+ request.attributes.each do |attr_info|
430
+ # Copy the aliases from the request so that reading
431
+ # the response in light of the request is easier
432
+ if attr_info.ns_alias.nil?
433
+ @aliases.add(attr_info.type_uri)
434
+ else
435
+ @aliases.add_alias(attr_info.type_uri, attr_info.ns_alias)
436
+ end
437
+ values = @data[attr_info.type_uri]
438
+ zero_value_types << attr_info if values.empty? # @data defaults to []
439
+
440
+ if attr_info.count != UNLIMITED_VALUES and attr_info.count < values.size
441
+ raise Error, "More than the number of requested values were specified for #{attr_info.type_uri.inspect}"
442
+ end
443
+ end
444
+ end
445
+
446
+ kv_args = _get_extension_kv_args(@aliases)
447
+
448
+ # Add the KV args into the response with the args that are
449
+ # unique to the fetch_response
450
+ ax_args = new_args
451
+
452
+ zero_value_types.each do |attr_info|
453
+ name = @aliases.get_alias(attr_info.type_uri)
454
+ kv_args["type." + name] = attr_info.type_uri
455
+ kv_args["count." + name] = "0"
456
+ end
457
+ update_url = (request and request.update_url or @update_url)
458
+ ax_args["update_url"] = update_url unless update_url.nil?
459
+ ax_args.update(kv_args)
460
+ ax_args
461
+ end
462
+
463
+ def parse_extension_args(ax_args)
464
+ super
465
+ @update_url = ax_args["update_url"]
466
+ end
467
+
468
+ # Construct a FetchResponse object from an OpenID library
469
+ # SuccessResponse object.
470
+ def self.from_success_response(success_response, signed = true)
471
+ obj = new
472
+ ax_args = if signed
473
+ success_response.get_signed_ns(obj.ns_uri)
474
+ else
475
+ success_response.message.get_args(obj.ns_uri)
476
+ end
477
+
478
+ begin
479
+ obj.parse_extension_args(ax_args)
480
+ obj
481
+ rescue Error
482
+ nil
483
+ end
484
+ end
485
+ end
486
+
487
+ # A store request attribute exchange message representation
488
+ class StoreRequest < KeyValueMessage
489
+ MODE = "store_request"
490
+
491
+ def initialize
492
+ super
493
+ @mode = MODE
494
+ end
495
+
496
+ # Extract a StoreRequest from an OpenID message
497
+ # message: OpenID::Message
498
+ # return a StoreRequest or nil if AX arguments are not present
499
+ def self.from_openid_request(oidreq)
500
+ message = oidreq.message
501
+ ax_args = message.get_args(NS_URI)
502
+ return if ax_args.empty? or ax_args["mode"] != MODE
503
+
504
+ req = new
505
+ req.parse_extension_args(ax_args)
506
+ req
507
+ end
508
+
509
+ def get_extension_args(aliases = nil)
510
+ ax_args = new_args
511
+ kv_args = _get_extension_kv_args(aliases)
512
+ ax_args.update(kv_args)
513
+ ax_args
514
+ end
515
+ end
516
+
517
+ # An indication that the store request was processed along with
518
+ # this OpenID transaction.
519
+ class StoreResponse < AXMessage
520
+ SUCCESS_MODE = "store_response_success"
521
+ FAILURE_MODE = "store_response_failure"
522
+ attr_reader :error_message
523
+
524
+ def initialize(succeeded = true, error_message = nil)
525
+ super()
526
+ raise Error, "Error message included in a success response" if succeeded and error_message
527
+
528
+ @mode = if succeeded
529
+ SUCCESS_MODE
530
+ else
531
+ FAILURE_MODE
532
+ end
533
+ @error_message = error_message
534
+ end
535
+
536
+ def self.from_success_response(success_response)
537
+ ax_args = success_response.message.get_args(NS_URI)
538
+ ax_args.key?("error") ? new(false, ax_args["error"]) : new
539
+ end
540
+
541
+ def succeeded?
542
+ @mode == SUCCESS_MODE
543
+ end
544
+
545
+ def get_extension_args
546
+ ax_args = new_args
547
+ ax_args["error"] = @error_message if !succeeded? and error_message
548
+ ax_args
549
+ end
550
+ end
551
+ end
552
+ end