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
@@ -0,0 +1,555 @@
1
+ require_relative "util"
2
+ require_relative "kvform"
3
+
4
+ module OpenID
5
+ IDENTIFIER_SELECT = "http://specs.openid.net/auth/2.0/identifier_select"
6
+
7
+ # URI for Simple Registration extension, the only commonly deployed
8
+ # OpenID 1.x extension, and so a special case.
9
+ SREG_URI = "http://openid.net/sreg/1.0"
10
+
11
+ # The OpenID 1.x namespace URIs
12
+ OPENID1_NS = "http://openid.net/signon/1.0"
13
+ OPENID11_NS = "http://openid.net/signon/1.1"
14
+ OPENID1_NAMESPACES = [OPENID1_NS, OPENID11_NS]
15
+
16
+ # The OpenID 2.0 namespace URI
17
+ OPENID2_NS = "http://specs.openid.net/auth/2.0"
18
+
19
+ # The namespace consisting of pairs with keys that are prefixed with
20
+ # "openid." but not in another namespace.
21
+ NULL_NAMESPACE = :null_namespace
22
+
23
+ # The null namespace, when it is an allowed OpenID namespace
24
+ OPENID_NS = :openid_namespace
25
+
26
+ # The top-level namespace, excluding all pairs with keys that start
27
+ # with "openid."
28
+ BARE_NS = :bare_namespace
29
+
30
+ # Limit, in bytes, of identity provider and return_to URLs,
31
+ # including response payload. See OpenID 1.1 specification,
32
+ # Appendix D.
33
+ OPENID1_URL_LIMIT = 2047
34
+
35
+ # All OpenID protocol fields. Used to check namespace aliases.
36
+ OPENID_PROTOCOL_FIELDS = %w[
37
+ ns
38
+ mode
39
+ error
40
+ return_to
41
+ contact
42
+ reference
43
+ signed
44
+ assoc_type
45
+ session_type
46
+ dh_modulus
47
+ dh_gen
48
+ dh_consumer_public
49
+ claimed_id
50
+ identity
51
+ realm
52
+ invalidate_handle
53
+ op_endpoint
54
+ response_nonce
55
+ sig
56
+ assoc_handle
57
+ trust_root
58
+ openid
59
+ ]
60
+
61
+ # Sentinel used for Message implementation to indicate that getArg
62
+ # should raise an exception instead of returning a default.
63
+ NO_DEFAULT = :no_default
64
+
65
+ # Raised if the generic OpenID namespace is accessed when there
66
+ # is no OpenID namespace set for this message.
67
+ class UndefinedOpenIDNamespace < Exception; end
68
+
69
+ # Raised when an alias or namespace URI has already been registered.
70
+ class NamespaceAliasRegistrationError < Exception; end
71
+
72
+ # Raised if openid.ns is not a recognized value.
73
+ # See Message class variable @@allowed_openid_namespaces
74
+ class InvalidOpenIDNamespace < Exception; end
75
+
76
+ class Message
77
+ attr_reader :namespaces
78
+
79
+ # Raised when key lookup fails
80
+ class KeyNotFound < IndexError; end
81
+
82
+ # Namespace / alias registration map. See
83
+ # register_namespace_alias.
84
+ @@registered_aliases = {}
85
+
86
+ # Registers a (namespace URI, alias) mapping in a global namespace
87
+ # alias map. Raises NamespaceAliasRegistrationError if either the
88
+ # namespace URI or alias has already been registered with a
89
+ # different value. This function is required if you want to use a
90
+ # namespace with an OpenID 1 message.
91
+ def self.register_namespace_alias(namespace_uri, alias_)
92
+ return if @@registered_aliases[alias_] == namespace_uri
93
+
94
+ if @@registered_aliases.values.include?(namespace_uri)
95
+ raise NamespaceAliasRegistrationError,
96
+ 'Namespace uri #{namespace_uri} already registered'
97
+ end
98
+
99
+ if @@registered_aliases.member?(alias_)
100
+ raise NamespaceAliasRegistrationError,
101
+ 'Alias #{alias_} already registered'
102
+ end
103
+
104
+ @@registered_aliases[alias_] = namespace_uri
105
+ end
106
+
107
+ @@allowed_openid_namespaces = [OPENID1_NS, OPENID2_NS, OPENID11_NS]
108
+
109
+ # Raises InvalidNamespaceError if you try to instantiate a Message
110
+ # with a namespace not in the above allowed list
111
+ def initialize(openid_namespace = nil)
112
+ @args = {}
113
+ @namespaces = NamespaceMap.new
114
+ if openid_namespace
115
+ implicit = OPENID1_NAMESPACES.member?(openid_namespace)
116
+ set_openid_namespace(openid_namespace, implicit)
117
+ else
118
+ @openid_ns_uri = nil
119
+ end
120
+ end
121
+
122
+ # Construct a Message containing a set of POST arguments.
123
+ # Raises InvalidNamespaceError if you try to instantiate a Message
124
+ # with a namespace not in the above allowed list
125
+ def self.from_post_args(args)
126
+ m = Message.new
127
+ openid_args = {}
128
+ args.each do |key, value|
129
+ if value.is_a?(Array)
130
+ raise ArgumentError, "Query dict must have one value for each key, " +
131
+ "not lists of values. Query is #{args.inspect}"
132
+ end
133
+
134
+ prefix, rest = key.split(".", 2)
135
+
136
+ if prefix != "openid" or rest.nil?
137
+ m.set_arg(BARE_NS, key, value)
138
+ else
139
+ openid_args[rest] = value
140
+ end
141
+ end
142
+
143
+ m._from_openid_args(openid_args)
144
+ m
145
+ end
146
+
147
+ # Construct a Message from a parsed KVForm message.
148
+ # Raises InvalidNamespaceError if you try to instantiate a Message
149
+ # with a namespace not in the above allowed list
150
+ def self.from_openid_args(openid_args)
151
+ m = Message.new
152
+ m._from_openid_args(openid_args)
153
+ m
154
+ end
155
+
156
+ # Raises InvalidNamespaceError if you try to instantiate a Message
157
+ # with a namespace not in the above allowed list
158
+ def _from_openid_args(openid_args)
159
+ ns_args = []
160
+
161
+ # resolve namespaces
162
+ openid_args.each do |rest, value|
163
+ ns_alias, ns_key = rest.split(".", 2)
164
+ if ns_key.nil?
165
+ ns_alias = NULL_NAMESPACE
166
+ ns_key = rest
167
+ end
168
+
169
+ if ns_alias == "ns"
170
+ @namespaces.add_alias(value, ns_key)
171
+ elsif ns_alias == NULL_NAMESPACE and ns_key == "ns"
172
+ set_openid_namespace(value, false)
173
+ else
174
+ ns_args << [ns_alias, ns_key, value]
175
+ end
176
+ end
177
+
178
+ # implicitly set an OpenID 1 namespace
179
+ set_openid_namespace(OPENID1_NS, true) unless get_openid_namespace
180
+
181
+ # put the pairs into the appropriate namespaces
182
+ ns_args.each do |ns_alias, ns_key, value|
183
+ ns_uri = @namespaces.get_namespace_uri(ns_alias)
184
+ unless ns_uri
185
+ ns_uri = _get_default_namespace(ns_alias)
186
+ if ns_uri
187
+ @namespaces.add_alias(ns_uri, ns_alias, true)
188
+ else
189
+ ns_uri = get_openid_namespace
190
+ ns_key = "#{ns_alias}.#{ns_key}"
191
+ end
192
+ end
193
+ set_arg(ns_uri, ns_key, value)
194
+ end
195
+ end
196
+
197
+ def _get_default_namespace(mystery_alias)
198
+ # only try to map an alias to a default if it's an
199
+ # OpenID 1.x namespace
200
+ @@registered_aliases[mystery_alias] if is_openid1
201
+ end
202
+
203
+ def set_openid_namespace(openid_ns_uri, implicit)
204
+ unless @@allowed_openid_namespaces.include?(openid_ns_uri)
205
+ raise InvalidOpenIDNamespace, "Invalid null namespace: #{openid_ns_uri}"
206
+ end
207
+
208
+ @namespaces.add_alias(openid_ns_uri, NULL_NAMESPACE, implicit)
209
+ @openid_ns_uri = openid_ns_uri
210
+ end
211
+
212
+ def get_openid_namespace
213
+ @openid_ns_uri
214
+ end
215
+
216
+ def is_openid1
217
+ OPENID1_NAMESPACES.member?(@openid_ns_uri)
218
+ end
219
+
220
+ def is_openid2
221
+ @openid_ns_uri == OPENID2_NS
222
+ end
223
+
224
+ # Create a message from a KVForm string
225
+ def self.from_kvform(kvform_string)
226
+ Message.from_openid_args(Util.kv_to_dict(kvform_string))
227
+ end
228
+
229
+ def copy
230
+ Marshal.load(Marshal.dump(self))
231
+ end
232
+
233
+ # Return all arguments with "openid." in from of namespaced arguments.
234
+ def to_post_args
235
+ args = {}
236
+
237
+ # add namespace defs to the output
238
+ @namespaces.each do |ns_uri, ns_alias|
239
+ next if @namespaces.implicit?(ns_uri)
240
+
241
+ ns_key = if ns_alias == NULL_NAMESPACE
242
+ "openid.ns"
243
+ else
244
+ "openid.ns." + ns_alias
245
+ end
246
+ args[ns_key] = ns_uri
247
+ end
248
+
249
+ @args.each do |k, value|
250
+ ns_uri, ns_key = k
251
+ key = get_key(ns_uri, ns_key)
252
+ args[key] = value
253
+ end
254
+
255
+ args
256
+ end
257
+
258
+ # Return all namespaced arguments, failing if any non-namespaced arguments
259
+ # exist.
260
+ def to_args
261
+ post_args = to_post_args
262
+ kvargs = {}
263
+ post_args.each do |k, v|
264
+ if !k.start_with?("openid.")
265
+ raise ArgumentError,
266
+ "This message can only be encoded as a POST, because it contains arguments that are not prefixed with 'openid.'"
267
+ else
268
+ kvargs[k[7..-1]] = v
269
+ end
270
+ end
271
+ kvargs
272
+ end
273
+
274
+ # Generate HTML form markup that contains the values in this
275
+ # message, to be HTTP POSTed as x-www-form-urlencoded UTF-8.
276
+ def to_form_markup(action_url, form_tag_attrs = nil, submit_text = "Continue")
277
+ form_tag_attr_map = {}
278
+
279
+ if form_tag_attrs
280
+ form_tag_attrs.each do |name, attr|
281
+ form_tag_attr_map[name] = attr
282
+ end
283
+ end
284
+
285
+ form_tag_attr_map["action"] = action_url
286
+ form_tag_attr_map["method"] = "post"
287
+ form_tag_attr_map["accept-charset"] = "UTF-8"
288
+ form_tag_attr_map["enctype"] = "application/x-www-form-urlencoded"
289
+
290
+ markup = "<form "
291
+
292
+ form_tag_attr_map.each do |k, v|
293
+ markup += " #{k}=\"#{v}\""
294
+ end
295
+
296
+ markup += ">\n"
297
+
298
+ to_post_args.each do |k, v|
299
+ markup += "<input type='hidden' name='#{k}' value='#{OpenID::Util.html_encode(v)}' />\n"
300
+ end
301
+ markup += "<input type='submit' value='#{submit_text}' />\n"
302
+ markup += "\n</form>"
303
+ markup
304
+ end
305
+
306
+ # Generate a GET URL with the paramters in this message attacked as
307
+ # query parameters.
308
+ def to_url(base_url)
309
+ Util.append_args(base_url, to_post_args)
310
+ end
311
+
312
+ # Generate a KVForm string that contains the parameters in this message.
313
+ # This will fail is the message contains arguments outside of the
314
+ # "openid." prefix.
315
+ def to_kvform
316
+ Util.dict_to_kv(to_args)
317
+ end
318
+
319
+ # Generate an x-www-urlencoded string.
320
+ def to_url_encoded
321
+ args = to_post_args.map.sort
322
+ Util.urlencode(args)
323
+ end
324
+
325
+ # Convert an input value into the internally used values of this obejct.
326
+ def _fix_ns(namespace)
327
+ if namespace == OPENID_NS
328
+ raise UndefinedOpenIDNamespace, "OpenID namespace not set" unless @openid_ns_uri
329
+
330
+ namespace = @openid_ns_uri
331
+
332
+ end
333
+
334
+ return namespace if namespace == BARE_NS
335
+
336
+ unless namespace.is_a?(String)
337
+ raise ArgumentError, "Namespace must be BARE_NS, OPENID_NS or " \
338
+ "a string. Got #{namespace.inspect}"
339
+ end
340
+
341
+ if namespace.index(":").nil?
342
+ msg = "OpenID 2.0 namespace identifiers SHOULD be URIs. " \
343
+ "Got #{namespace.inspect}"
344
+ Util.log(msg)
345
+
346
+ if namespace == "sreg"
347
+ msg = "Using #{SREG_URI} instead of \"sreg\" as namespace"
348
+ Util.log(msg)
349
+ return SREG_URI
350
+ end
351
+ end
352
+
353
+ namespace
354
+ end
355
+
356
+ def has_key?(namespace, ns_key)
357
+ namespace = _fix_ns(namespace)
358
+ @args.member?([namespace, ns_key])
359
+ end
360
+
361
+ # Get the key for a particular namespaced argument
362
+ def get_key(namespace, ns_key)
363
+ namespace = _fix_ns(namespace)
364
+ return ns_key if namespace == BARE_NS
365
+
366
+ ns_alias = @namespaces.get_alias(namespace)
367
+
368
+ # no alias is defined, so no key can exist
369
+ return if ns_alias.nil?
370
+
371
+ tail = if ns_alias == NULL_NAMESPACE
372
+ ns_key
373
+ else
374
+ "#{ns_alias}.#{ns_key}"
375
+ end
376
+
377
+ "openid." + tail
378
+ end
379
+
380
+ # Get a value for a namespaced key.
381
+ def get_arg(namespace, key, default = nil)
382
+ namespace = _fix_ns(namespace)
383
+ @args.fetch([namespace, key]) do
384
+ raise KeyNotFound, "<#{namespace}>#{key} not in this message" if default == NO_DEFAULT
385
+
386
+ default
387
+ end
388
+ end
389
+
390
+ # Get the arguments that are defined for this namespace URI.
391
+ def get_args(namespace)
392
+ namespace = _fix_ns(namespace)
393
+ args = {}
394
+ @args.each do |k, v|
395
+ pair_ns, ns_key = k
396
+ args[ns_key] = v if pair_ns == namespace
397
+ end
398
+ args
399
+ end
400
+
401
+ # Set multiple key/value pairs in one call.
402
+ def update_args(namespace, updates)
403
+ namespace = _fix_ns(namespace)
404
+ updates.each { |k, v| set_arg(namespace, k, v) }
405
+ end
406
+
407
+ # Set a single argument in this namespace
408
+ def set_arg(namespace, key, value)
409
+ namespace = _fix_ns(namespace)
410
+ @args[[namespace, key].freeze] = value
411
+ @namespaces.add(namespace) if namespace != BARE_NS
412
+ end
413
+
414
+ # Remove a single argument from this namespace.
415
+ def del_arg(namespace, key)
416
+ namespace = _fix_ns(namespace)
417
+ _key = [namespace, key]
418
+ @args.delete(_key)
419
+ end
420
+
421
+ def ==(other)
422
+ other.is_a?(self.class) && @args == other.instance_eval { @args }
423
+ end
424
+
425
+ def get_aliased_arg(aliased_key, default = nil)
426
+ return get_openid_namespace if aliased_key == "ns"
427
+
428
+ ns_alias, key = aliased_key.split(".", 2)
429
+ if ns_alias == "ns"
430
+ uri = @namespaces.get_namespace_uri(key)
431
+ return (uri.nil? ? default : uri) unless uri.nil? and default == NO_DEFAULT
432
+
433
+ raise KeyNotFound, "Namespace #{key} not defined when looking " \
434
+ "for #{aliased_key}"
435
+
436
+ end
437
+
438
+ if key.nil?
439
+ key = aliased_key
440
+ ns = nil
441
+ else
442
+ ns = @namespaces.get_namespace_uri(ns_alias)
443
+ end
444
+
445
+ if ns.nil?
446
+ key = aliased_key
447
+ ns = get_openid_namespace
448
+ end
449
+
450
+ get_arg(ns, key, default)
451
+ end
452
+ end
453
+
454
+ # Maintains a bidirectional map between namespace URIs and aliases.
455
+ class NamespaceMap
456
+ def initialize
457
+ @alias_to_namespace = {}
458
+ @namespace_to_alias = {}
459
+ @implicit_namespaces = []
460
+ end
461
+
462
+ def get_alias(namespace_uri)
463
+ @namespace_to_alias[namespace_uri]
464
+ end
465
+
466
+ def get_namespace_uri(namespace_alias)
467
+ @alias_to_namespace[namespace_alias]
468
+ end
469
+
470
+ # Add an alias from this namespace URI to the alias.
471
+ def add_alias(namespace_uri, desired_alias, implicit = false)
472
+ # Check that desired_alias is not an openid protocol field as
473
+ # per the spec.
474
+ Util.truthy_assert(
475
+ !OPENID_PROTOCOL_FIELDS.include?(desired_alias),
476
+ "#{desired_alias} is not an allowed namespace alias",
477
+ )
478
+
479
+ # check that there is not a namespace already defined for the
480
+ # desired alias
481
+ current_namespace_uri = @alias_to_namespace.fetch(desired_alias, nil)
482
+ if current_namespace_uri and current_namespace_uri != namespace_uri
483
+ raise IndexError,
484
+ "Cannot map #{namespace_uri} to alias #{desired_alias}. #{current_namespace_uri} is already mapped to alias #{desired_alias}"
485
+ end
486
+
487
+ # Check that desired_alias does not contain a period as per the
488
+ # spec.
489
+ if desired_alias.is_a?(String)
490
+ Util.truthy_assert(
491
+ desired_alias.index(".").nil?,
492
+ "#{desired_alias} must not contain a dot",
493
+ )
494
+ end
495
+
496
+ # check that there is not already a (different) alias for this
497
+ # namespace URI.
498
+ _alias = @namespace_to_alias[namespace_uri]
499
+ if _alias and _alias != desired_alias
500
+ raise IndexError,
501
+ "Cannot map #{namespace_uri} to alias #{desired_alias}. It is already mapped to alias #{_alias}"
502
+ end
503
+
504
+ @alias_to_namespace[desired_alias] = namespace_uri
505
+ @namespace_to_alias[namespace_uri] = desired_alias
506
+ @implicit_namespaces << namespace_uri if implicit
507
+ desired_alias
508
+ end
509
+
510
+ # Add this namespace URI to the mapping, without caring what alias
511
+ # it ends up with.
512
+ def add(namespace_uri)
513
+ # see if this namepace is already mapped to an alias
514
+ _alias = @namespace_to_alias[namespace_uri]
515
+ return _alias if _alias
516
+
517
+ # Fall back to generating a numberical alias
518
+ i = 0
519
+ while true
520
+ _alias = "ext" + i.to_s
521
+ begin
522
+ add_alias(namespace_uri, _alias)
523
+ rescue IndexError
524
+ i += 1
525
+ else
526
+ return _alias
527
+ end
528
+ end
529
+
530
+ raise StandardError, "Unreachable"
531
+ end
532
+
533
+ def member?(namespace_uri)
534
+ @namespace_to_alias.has_key?(namespace_uri)
535
+ end
536
+
537
+ def each(&block)
538
+ @namespace_to_alias.each(&block)
539
+ end
540
+
541
+ def namespace_uris
542
+ # Return an iterator over the namespace URIs
543
+ @namespace_to_alias.keys
544
+ end
545
+
546
+ def implicit?(namespace_uri)
547
+ @implicit_namespaces.member?(namespace_uri)
548
+ end
549
+
550
+ def aliases
551
+ # Return an iterator over the aliases
552
+ @alias_to_namespace.keys
553
+ end
554
+ end
555
+ end
@@ -0,0 +1,7 @@
1
+ require_relative "util"
2
+
3
+ module OpenID
4
+ # An error in the OpenID protocol
5
+ class ProtocolError < OpenIDError
6
+ end
7
+ end