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,597 @@
1
+ # stdlib
2
+ require "uri"
3
+
4
+ # This library
5
+ require_relative "urinorm"
6
+
7
+ module OpenID
8
+ class RealmVerificationRedirected < Exception
9
+ # Attempting to verify this realm resulted in a redirect.
10
+ def initialize(relying_party_url, rp_url_after_redirects)
11
+ @relying_party_url = relying_party_url
12
+ @rp_url_after_redirects = rp_url_after_redirects
13
+ end
14
+
15
+ def to_s
16
+ "Attempting to verify #{@relying_party_url} resulted in " +
17
+ "redirect to #{@rp_url_after_redirects}"
18
+ end
19
+ end
20
+
21
+ module TrustRoot
22
+ TOP_LEVEL_DOMAINS = %w[
23
+ ac
24
+ ad
25
+ ae
26
+ aero
27
+ af
28
+ ag
29
+ ai
30
+ al
31
+ am
32
+ an
33
+ ao
34
+ aq
35
+ ar
36
+ arpa
37
+ as
38
+ asia
39
+ at
40
+ au
41
+ aw
42
+ ax
43
+ az
44
+ ba
45
+ bb
46
+ bd
47
+ be
48
+ bf
49
+ bg
50
+ bh
51
+ bi
52
+ biz
53
+ bj
54
+ bm
55
+ bn
56
+ bo
57
+ br
58
+ bs
59
+ bt
60
+ bv
61
+ bw
62
+ by
63
+ bz
64
+ ca
65
+ cat
66
+ cc
67
+ cd
68
+ cf
69
+ cg
70
+ ch
71
+ ci
72
+ ck
73
+ cl
74
+ cm
75
+ cn
76
+ co
77
+ com
78
+ coop
79
+ cr
80
+ cu
81
+ cv
82
+ cx
83
+ cy
84
+ cz
85
+ de
86
+ dj
87
+ dk
88
+ dm
89
+ do
90
+ dz
91
+ ec
92
+ edu
93
+ ee
94
+ eg
95
+ er
96
+ es
97
+ et
98
+ eu
99
+ fi
100
+ fj
101
+ fk
102
+ fm
103
+ fo
104
+ fr
105
+ ga
106
+ gb
107
+ gd
108
+ ge
109
+ gf
110
+ gg
111
+ gh
112
+ gi
113
+ gl
114
+ gm
115
+ gn
116
+ gov
117
+ gp
118
+ gq
119
+ gr
120
+ gs
121
+ gt
122
+ gu
123
+ gw
124
+ gy
125
+ hk
126
+ hm
127
+ hn
128
+ hr
129
+ ht
130
+ hu
131
+ id
132
+ ie
133
+ il
134
+ im
135
+ in
136
+ info
137
+ int
138
+ io
139
+ iq
140
+ ir
141
+ is
142
+ it
143
+ je
144
+ jm
145
+ jo
146
+ jobs
147
+ jp
148
+ ke
149
+ kg
150
+ kh
151
+ ki
152
+ km
153
+ kn
154
+ kp
155
+ kr
156
+ kw
157
+ ky
158
+ kz
159
+ la
160
+ lb
161
+ lc
162
+ li
163
+ lk
164
+ lr
165
+ ls
166
+ lt
167
+ lu
168
+ lv
169
+ ly
170
+ ma
171
+ mc
172
+ md
173
+ me
174
+ mg
175
+ mh
176
+ mil
177
+ mk
178
+ ml
179
+ mm
180
+ mn
181
+ mo
182
+ mobi
183
+ mp
184
+ mq
185
+ mr
186
+ ms
187
+ mt
188
+ mu
189
+ museum
190
+ mv
191
+ mw
192
+ mx
193
+ my
194
+ mz
195
+ na
196
+ name
197
+ nc
198
+ ne
199
+ net
200
+ nf
201
+ ng
202
+ ni
203
+ nl
204
+ no
205
+ np
206
+ nr
207
+ nu
208
+ nz
209
+ om
210
+ org
211
+ pa
212
+ pe
213
+ pf
214
+ pg
215
+ ph
216
+ pk
217
+ pl
218
+ pm
219
+ pn
220
+ pr
221
+ pro
222
+ ps
223
+ pt
224
+ pw
225
+ py
226
+ qa
227
+ re
228
+ ro
229
+ rs
230
+ ru
231
+ rw
232
+ sa
233
+ sb
234
+ sc
235
+ sd
236
+ se
237
+ sg
238
+ sh
239
+ si
240
+ sj
241
+ sk
242
+ sl
243
+ sm
244
+ sn
245
+ so
246
+ sr
247
+ st
248
+ su
249
+ sv
250
+ sy
251
+ sz
252
+ tc
253
+ td
254
+ tel
255
+ tf
256
+ tg
257
+ th
258
+ tj
259
+ tk
260
+ tl
261
+ tm
262
+ tn
263
+ to
264
+ tp
265
+ tr
266
+ travel
267
+ tt
268
+ tv
269
+ tw
270
+ tz
271
+ ua
272
+ ug
273
+ uk
274
+ us
275
+ uy
276
+ uz
277
+ va
278
+ vc
279
+ ve
280
+ vg
281
+ vi
282
+ vn
283
+ vu
284
+ wf
285
+ ws
286
+ xn--0zwm56d
287
+ xn--11b5bs3a9aj6g
288
+ xn--80akhbyknj4f
289
+ xn--9t4b11yi5a
290
+ xn--deba0ad
291
+ xn--g6w251d
292
+ xn--hgbk6aj7f53bba
293
+ xn--hlcj6aya9esc7a
294
+ xn--jxalpdlp
295
+ xn--kgbechtv
296
+ xn--zckzah
297
+ ye
298
+ yt
299
+ yu
300
+ za
301
+ zm
302
+ zw
303
+ ]
304
+
305
+ ALLOWED_PROTOCOLS = %w[http https]
306
+
307
+ # The URI for relying party discovery, used in realm verification.
308
+ #
309
+ # XXX: This should probably live somewhere else (like in
310
+ # OpenID or OpenID::Yadis somewhere)
311
+ RP_RETURN_TO_URL_TYPE = "http://specs.openid.net/auth/2.0/return_to"
312
+
313
+ # If the endpoint is a relying party OpenID return_to endpoint,
314
+ # return the endpoint URL. Otherwise, return None.
315
+ #
316
+ # This function is intended to be used as a filter for the Yadis
317
+ # filtering interface.
318
+ #
319
+ # endpoint: An XRDS BasicServiceEndpoint, as returned by
320
+ # performing Yadis dicovery.
321
+ #
322
+ # returns the endpoint URL or None if the endpoint is not a
323
+ # relying party endpoint.
324
+ def self._extract_return_url(endpoint)
325
+ return endpoint.uri if endpoint.matchTypes([RP_RETURN_TO_URL_TYPE])
326
+
327
+ nil
328
+ end
329
+
330
+ # Is the return_to URL under one of the supplied allowed
331
+ # return_to URLs?
332
+ def self.return_to_matches(allowed_return_to_urls, return_to)
333
+ allowed_return_to_urls.each do |allowed_return_to|
334
+ # A return_to pattern works the same as a realm, except that
335
+ # it's not allowed to use a wildcard. We'll model this by
336
+ # parsing it as a realm, and not trying to match it if it has
337
+ # a wildcard.
338
+
339
+ return_realm = TrustRoot.parse(allowed_return_to)
340
+ if !return_realm.nil? and
341
+
342
+ # Does not have a wildcard
343
+ !return_realm.wildcard and
344
+
345
+ # Matches the return_to that we passed in with it
346
+ return_realm.validate_url(return_to) # Parses as a trust root
347
+
348
+ return true
349
+ end
350
+ end
351
+
352
+ # No URL in the list matched
353
+ false
354
+ end
355
+
356
+ # Given a relying party discovery URL return a list of return_to
357
+ # URLs.
358
+ def self.get_allowed_return_urls(relying_party_url)
359
+ rp_url_after_redirects, return_to_urls = services.get_service_endpoints(
360
+ relying_party_url, _extract_return_url
361
+ )
362
+
363
+ if rp_url_after_redirects != relying_party_url
364
+ # Verification caused a redirect
365
+ raise RealmVerificationRedirected.new(
366
+ relying_party_url, rp_url_after_redirects
367
+ )
368
+ end
369
+
370
+ return_to_urls
371
+ end
372
+
373
+ # Verify that a return_to URL is valid for the given realm.
374
+ #
375
+ # This function builds a discovery URL, performs Yadis discovery
376
+ # on it, makes sure that the URL does not redirect, parses out
377
+ # the return_to URLs, and finally checks to see if the current
378
+ # return_to URL matches the return_to.
379
+ #
380
+ # raises DiscoveryFailure when Yadis discovery fails returns
381
+ # true if the return_to URL is valid for the realm
382
+ def self.verify_return_to(realm_str, return_to, _vrfy = nil)
383
+ # _vrfy parameter is there to make testing easier
384
+ _vrfy = method(:get_allowed_return_urls) if _vrfy.nil?
385
+
386
+ raise ArgumentError, "_vrfy must be a Proc or Method" unless _vrfy.is_a?(Proc) or _vrfy.is_a?(Method)
387
+
388
+ realm = TrustRoot.parse(realm_str)
389
+ if realm.nil?
390
+ # The realm does not parse as a URL pattern
391
+ return false
392
+ end
393
+
394
+ begin
395
+ allowable_urls = _vrfy.call(realm.build_discovery_url)
396
+ rescue RealmVerificationRedirected => e
397
+ Util.log(e.to_s)
398
+ return false
399
+ end
400
+
401
+ return true if return_to_matches(allowable_urls, return_to)
402
+
403
+ Util.log("Failed to validate return_to #{return_to} for " +
404
+ "realm #{realm_str}, was not in #{allowable_urls}")
405
+ false
406
+ end
407
+
408
+ class TrustRoot
409
+ attr_reader :unparsed, :proto, :wildcard, :host, :port, :path
410
+
411
+ @@empty_re = Regexp.new('^http[s]*:\/\/\*\/$')
412
+
413
+ def self._build_path(path, query = nil, frag = nil)
414
+ s = path.dup
415
+
416
+ frag = nil if frag == ""
417
+ query = nil if query == ""
418
+
419
+ s << "?" << query if query
420
+
421
+ s << "#" << frag if frag
422
+
423
+ s
424
+ end
425
+
426
+ def self._parse_url(url)
427
+ begin
428
+ url = URINorm.urinorm(url)
429
+ rescue URI::InvalidURIError
430
+ nil
431
+ end
432
+
433
+ begin
434
+ parsed = URI::DEFAULT_PARSER.parse(url)
435
+ rescue URI::InvalidURIError
436
+ return
437
+ end
438
+
439
+ path = TrustRoot._build_path(
440
+ parsed.path,
441
+ parsed.query,
442
+ parsed.fragment,
443
+ )
444
+
445
+ [
446
+ parsed.scheme || "",
447
+ parsed.host || "",
448
+ parsed.port || "",
449
+ path || "",
450
+ ]
451
+ end
452
+
453
+ def self.parse(trust_root)
454
+ trust_root = trust_root.dup
455
+ unparsed = trust_root.dup
456
+
457
+ # look for wildcard
458
+ wildcard = !trust_root.index("://*.").nil?
459
+ trust_root.sub!("*.", "") if wildcard
460
+
461
+ # handle http://*/ case
462
+ if !wildcard and @@empty_re.match(trust_root)
463
+ proto = trust_root.split(":")[0]
464
+ port = (proto == "http") ? 80 : 443
465
+ return new(unparsed, proto, true, "", port, "/")
466
+ end
467
+
468
+ parts = TrustRoot._parse_url(trust_root)
469
+ return if parts.nil?
470
+
471
+ proto, host, port, path = parts
472
+ return if host[0] == "."
473
+
474
+ # check for URI fragment
475
+ return if path and !path.index("#").nil?
476
+
477
+ return unless %w[http https].member?(proto)
478
+
479
+ new(unparsed, proto, wildcard, host, port, path)
480
+ end
481
+
482
+ def self.check_sanity(trust_root_string)
483
+ trust_root = TrustRoot.parse(trust_root_string)
484
+ return false if trust_root.nil?
485
+
486
+ trust_root.sane?
487
+ end
488
+
489
+ # quick func for validating a url against a trust root. See the
490
+ # TrustRoot class if you need more control.
491
+ def self.check_url(trust_root, url)
492
+ tr = parse(trust_root)
493
+ (!tr.nil? and tr.validate_url(url))
494
+ end
495
+
496
+ # Return a discovery URL for this realm.
497
+ #
498
+ # This function does not check to make sure that the realm is
499
+ # valid. Its behaviour on invalid inputs is undefined.
500
+ #
501
+ # return_to:: The relying party return URL of the OpenID
502
+ # authentication request
503
+ #
504
+ # Returns the URL upon which relying party discovery should be
505
+ # run in order to verify the return_to URL
506
+ def build_discovery_url
507
+ return @unparsed unless wildcard
508
+
509
+ # Use "www." in place of the star
510
+ www_domain = "www." + @host
511
+ port = (!@port.nil? and ![80, 443].member?(@port)) ? (":" + @port.to_s) : ""
512
+ "#{@proto}://#{www_domain}#{port}#{@path}"
513
+ end
514
+
515
+ def initialize(unparsed, proto, wildcard, host, port, path)
516
+ @unparsed = unparsed
517
+ @proto = proto
518
+ @wildcard = wildcard
519
+ @host = host
520
+ @port = port
521
+ @path = path
522
+ end
523
+
524
+ def sane?
525
+ return true if @host == "localhost"
526
+
527
+ host_parts = @host.split(".")
528
+
529
+ # a note: ruby string split does not put an empty string at
530
+ # the end of the list if the split element is last. for
531
+ # example, 'foo.com.'.split('.') => ['foo','com']. Mentioned
532
+ # because the python code differs here.
533
+
534
+ return false if host_parts.length == 0
535
+
536
+ # no adjacent dots
537
+ return false if host_parts.member?("")
538
+
539
+ # last part must be a tld
540
+ tld = host_parts[-1]
541
+ return false unless TOP_LEVEL_DOMAINS.member?(tld)
542
+
543
+ return false if host_parts.length == 1
544
+
545
+ if @wildcard && (tld.length == 2 and host_parts[-2].length <= 3)
546
+ # It's a 2-letter tld with a short second to last segment
547
+ # so there needs to be more than two segments specified
548
+ # (e.g. *.co.uk is insane)
549
+ return host_parts.length > 2
550
+ end
551
+
552
+ true
553
+ end
554
+
555
+ def validate_url(url)
556
+ parts = TrustRoot._parse_url(url)
557
+ return false if parts.nil?
558
+
559
+ proto, host, port, path = parts
560
+
561
+ return false unless proto == @proto
562
+ return false unless port == @port
563
+ return false unless host.index("*").nil?
564
+
565
+ if !@wildcard
566
+ return false if host != @host
567
+ elsif (@host != "") and
568
+ !host.end_with?("." + @host) and
569
+ (host != @host)
570
+ return false
571
+ end
572
+
573
+ if path != @path
574
+ path_len = @path.length
575
+ trust_prefix = @path[0...path_len]
576
+ url_prefix = path[0...path_len]
577
+
578
+ # must be equal up to the length of the path, at least
579
+ return false if trust_prefix != url_prefix
580
+
581
+ # These characters must be on the boundary between the end
582
+ # of the trust root's path and the start of the URL's path.
583
+ allowed = if !@path.index("?").nil?
584
+ "&"
585
+ else
586
+ "?/"
587
+ end
588
+
589
+ return (!allowed.index(@path[-1]).nil? or
590
+ !allowed.index(path[path_len]).nil?)
591
+ end
592
+
593
+ true
594
+ end
595
+ end
596
+ end
597
+ end
@@ -0,0 +1,72 @@
1
+ # stdlib
2
+ require "uri"
3
+
4
+ module OpenID
5
+ module URINorm
6
+ VALID_URI_SCHEMES = %w[http https].freeze
7
+
8
+ def self.urinorm(uri)
9
+ uri = URI.parse(uri)
10
+
11
+ raise URI::InvalidURIError.new("no scheme") unless uri.scheme
12
+
13
+ uri.scheme = uri.scheme.downcase
14
+ raise URI::InvalidURIError.new("Not an HTTP or HTTPS URI") unless VALID_URI_SCHEMES.member?(uri.scheme)
15
+
16
+ raise URI::InvalidURIError.new("no host") if uri.host.nil? # For Ruby 2.7
17
+
18
+ raise URI::InvalidURIError.new("no host") if uri.host.empty? # For Ruby 3+
19
+
20
+ uri.host = uri.host.downcase
21
+
22
+ uri.path = remove_dot_segments(uri.path)
23
+ uri.path = "/" if uri.path.empty?
24
+
25
+ uri = uri.normalize.to_s
26
+ uri.gsub(PERCENT_ESCAPE_RE) do
27
+ sub = ::Regexp.last_match(0)[1..2].to_i(16).chr
28
+ reserved(sub) ? ::Regexp.last_match(0).upcase : sub
29
+ end
30
+ end
31
+
32
+ RESERVED_RE = /[A-Za-z0-9._~-]/
33
+ PERCENT_ESCAPE_RE = /%[0-9a-zA-Z]{2}/
34
+
35
+ def self.reserved(chr)
36
+ !(RESERVED_RE =~ chr)
37
+ end
38
+
39
+ def self.remove_dot_segments(path)
40
+ result_segments = []
41
+
42
+ while path.length > 0
43
+ if path.start_with?("../")
44
+ path = path[3..-1]
45
+ elsif path.start_with?("./")
46
+ path = path[2..-1]
47
+ elsif path.start_with?("/./")
48
+ path = path[2..-1]
49
+ elsif path == "/."
50
+ path = "/"
51
+ elsif path.start_with?("/../")
52
+ path = path[3..-1]
53
+ result_segments.pop if result_segments.length > 0
54
+ elsif path == "/.."
55
+ path = "/"
56
+ result_segments.pop if result_segments.length > 0
57
+ elsif ["..", "."].include?(path)
58
+ path = ""
59
+ else
60
+ i = 0
61
+ i = 1 if path[0].chr == "/"
62
+ i = path.index("/", i)
63
+ i = path.length if i.nil?
64
+ result_segments << path[0...i]
65
+ path = path[i..-1]
66
+ end
67
+ end
68
+
69
+ result_segments.join("")
70
+ end
71
+ end
72
+ end