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
@@ -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