ruby-openid2 3.0.0

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