ruby-openid 2.1.7 → 2.1.8

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ruby-openid might be problematic. Click here for more details.

Files changed (38) hide show
  1. data/CHANGELOG +207 -27
  2. data/NOTICE +1 -1
  3. data/README +3 -4
  4. data/examples/discover +0 -0
  5. data/examples/rails_openid/app/controllers/consumer_controller.rb +1 -1
  6. data/examples/rails_openid/script/about +0 -0
  7. data/examples/rails_openid/script/breakpointer +0 -0
  8. data/examples/rails_openid/script/console +0 -0
  9. data/examples/rails_openid/script/destroy +0 -0
  10. data/examples/rails_openid/script/generate +0 -0
  11. data/examples/rails_openid/script/performance/benchmarker +0 -0
  12. data/examples/rails_openid/script/performance/profiler +0 -0
  13. data/examples/rails_openid/script/plugin +0 -0
  14. data/examples/rails_openid/script/process/reaper +0 -0
  15. data/examples/rails_openid/script/process/spawner +0 -0
  16. data/examples/rails_openid/script/process/spinner +0 -0
  17. data/examples/rails_openid/script/runner +0 -0
  18. data/examples/rails_openid/script/server +0 -0
  19. data/lib/openid.rb +1 -1
  20. data/lib/openid/association.rb +2 -2
  21. data/lib/openid/consumer.rb +1 -1
  22. data/lib/openid/consumer/associationmanager.rb +1 -1
  23. data/lib/openid/consumer/discovery.rb +1 -2
  24. data/lib/openid/consumer/html_parse.rb +1 -1
  25. data/lib/openid/consumer/idres.rb +3 -3
  26. data/lib/openid/consumer/responses.rb +1 -1
  27. data/lib/openid/cryptutil.rb +22 -4
  28. data/lib/openid/extensions/ax.rb +28 -5
  29. data/lib/openid/extensions/oauth.rb +91 -0
  30. data/lib/openid/fetchers.rb +22 -2
  31. data/lib/openid/yadis/xrires.rb +4 -11
  32. data/test/data/test_discover/openid_utf8.html +11 -0
  33. data/test/test_ax.rb +44 -2
  34. data/test/test_discover.rb +14 -0
  35. data/test/test_fetchers.rb +28 -1
  36. data/test/test_idres.rb +77 -36
  37. data/test/test_oauth.rb +175 -0
  38. metadata +206 -226
@@ -110,10 +110,12 @@ module OpenID
110
110
  class FetchRequest < AXMessage
111
111
  attr_reader :requested_attributes
112
112
  attr_accessor :update_url
113
+
114
+ MODE = 'fetch_request'
113
115
 
114
116
  def initialize(update_url = nil)
115
117
  super()
116
- @mode = 'fetch_request'
118
+ @mode = MODE
117
119
  @requested_attributes = {}
118
120
  @update_url = update_url
119
121
  end
@@ -180,7 +182,7 @@ module OpenID
180
182
  def self.from_openid_request(oidreq)
181
183
  message = oidreq.message
182
184
  ax_args = message.get_args(NS_URI)
183
- return nil if ax_args == {}
185
+ return nil if ax_args == {} or ax_args['mode'] != MODE
184
186
  req = new
185
187
  req.parse_extension_args(ax_args)
186
188
 
@@ -467,11 +469,26 @@ module OpenID
467
469
 
468
470
  # A store request attribute exchange message representation
469
471
  class StoreRequest < KeyValueMessage
472
+
473
+ MODE = 'store_request'
474
+
470
475
  def initialize
471
476
  super
472
- @mode = 'store_request'
477
+ @mode = MODE
473
478
  end
474
-
479
+
480
+ # Extract a StoreRequest from an OpenID message
481
+ # message: OpenID::Message
482
+ # return a StoreRequest or nil if AX arguments are not present
483
+ def self.from_openid_request(oidreq)
484
+ message = oidreq.message
485
+ ax_args = message.get_args(NS_URI)
486
+ return nil if ax_args.empty? or ax_args['mode'] != MODE
487
+ req = new
488
+ req.parse_extension_args(ax_args)
489
+ req
490
+ end
491
+
475
492
  def get_extension_args(aliases=nil)
476
493
  ax_args = new_args
477
494
  kv_args = _get_extension_kv_args(aliases)
@@ -499,7 +516,13 @@ module OpenID
499
516
  end
500
517
  @error_message = error_message
501
518
  end
502
-
519
+
520
+ def self.from_success_response(success_response)
521
+ resp = nil
522
+ ax_args = success_response.message.get_args(NS_URI)
523
+ resp = ax_args.key?('error') ? new(false, ax_args['error']) : new
524
+ end
525
+
503
526
  def succeeded?
504
527
  @mode == SUCCESS_MODE
505
528
  end
@@ -0,0 +1,91 @@
1
+ # An implementation of the OpenID OAuth Extension
2
+ # Extension 1.0
3
+ # see: http://openid.net/specs/
4
+
5
+ require 'openid/extension'
6
+
7
+ module OpenID
8
+
9
+ module OAuth
10
+ NS_URI = "http://specs.openid.net/extensions/oauth/1.0"
11
+ # An OAuth token request, sent from a relying
12
+ # party to a provider
13
+ class Request < Extension
14
+ attr_accessor :consumer, :scope, :ns_alias, :ns_uri
15
+ def initialize(consumer=nil, scope=nil)
16
+ @ns_alias = 'oauth'
17
+ @ns_uri = NS_URI
18
+ @consumer = consumer
19
+ @scope = scope
20
+ end
21
+
22
+
23
+ def get_extension_args
24
+ ns_args = {}
25
+ ns_args['consumer'] = @consumer if @consumer
26
+ ns_args['scope'] = @scope if @scope
27
+ return ns_args
28
+ end
29
+
30
+ # Instantiate a Request object from the arguments in a
31
+ # checkid_* OpenID message
32
+ # return nil if the extension was not requested.
33
+ def self.from_openid_request(oid_req)
34
+ oauth_req = new
35
+ args = oid_req.message.get_args(NS_URI)
36
+ if args == {}
37
+ return nil
38
+ end
39
+ oauth_req.parse_extension_args(args)
40
+ return oauth_req
41
+ end
42
+
43
+ # Set the state of this request to be that expressed in these
44
+ # OAuth arguments
45
+ def parse_extension_args(args)
46
+ @consumer = args["consumer"]
47
+ @scope = args["scope"]
48
+ end
49
+
50
+ end
51
+
52
+ # A OAuth request token response, sent from a provider
53
+ # to a relying party
54
+ class Response < Extension
55
+ attr_accessor :request_token, :scope
56
+ def initialize(request_token=nil, scope=nil)
57
+ @ns_alias = 'oauth'
58
+ @ns_uri = NS_URI
59
+ @request_token = request_token
60
+ @scope = scope
61
+ end
62
+
63
+ # Create a Response object from an OpenID::Consumer::SuccessResponse
64
+ def self.from_success_response(success_response)
65
+ args = success_response.get_signed_ns(NS_URI)
66
+ return nil if args.nil?
67
+ oauth_resp = new
68
+ oauth_resp.parse_extension_args(args)
69
+ return oauth_resp
70
+ end
71
+
72
+ # parse the oauth request arguments into the
73
+ # internal state of this object
74
+ # if strict is specified, raise an exception when bad data is
75
+ # encountered
76
+ def parse_extension_args(args, strict=false)
77
+ @request_token = args["request_token"]
78
+ @scope = args["scope"]
79
+ end
80
+
81
+ def get_extension_args
82
+ ns_args = {}
83
+ ns_args['request_token'] = @request_token if @request_token
84
+ ns_args['scope'] = @scope if @scope
85
+ return ns_args
86
+ end
87
+
88
+ end
89
+ end
90
+
91
+ end
@@ -205,6 +205,9 @@ module OpenID
205
205
  conn.request_post(url.request_uri, body, headers)
206
206
  end
207
207
  }
208
+ setup_encoding(response)
209
+ rescue Timeout::Error => why
210
+ raise FetchingError, "Error fetching #{url}: #{why}"
208
211
  rescue RuntimeError => why
209
212
  raise why
210
213
  rescue OpenSSL::SSL::SSLError => why
@@ -212,8 +215,6 @@ module OpenID
212
215
  rescue FetchingError => why
213
216
  raise why
214
217
  rescue Exception => why
215
- # Things we've caught here include a Timeout::Error, which descends
216
- # from SignalException.
217
218
  raise FetchingError, "Error fetching #{url}: #{why}"
218
219
  end
219
220
 
@@ -234,5 +235,24 @@ module OpenID
234
235
  return HTTPResponse._from_net_response(response, unparsed_url)
235
236
  end
236
237
  end
238
+
239
+ private
240
+ def setup_encoding(response)
241
+ return unless defined?(::Encoding::ASCII_8BIT)
242
+ charset = response.type_params["charset"]
243
+ return if charset.nil?
244
+ encoding = nil
245
+ begin
246
+ encoding = Encoding.find(charset)
247
+ rescue ArgumentError
248
+ end
249
+ encoding ||= Encoding::ASCII_8BIT
250
+ body = response.body
251
+ if body.respond_to?(:force_encoding)
252
+ body.force_encoding(encoding)
253
+ else
254
+ body.set_encoding(encoding)
255
+ end
256
+ end
237
257
  end
238
258
  end
@@ -42,34 +42,27 @@ module OpenID
42
42
  return XRI.append_args(hxri, args)
43
43
  end
44
44
 
45
- def query(xri, service_types)
45
+ def query(xri)
46
46
  # these can be query args or http headers, needn't be both.
47
47
  # headers = {'Accept' => 'application/xrds+xml;sep=true'}
48
48
  canonicalID = nil
49
49
 
50
- services = service_types.collect { |service_type|
51
- url = self.query_url(xri, service_type)
50
+ url = self.query_url(xri)
52
51
  begin
53
52
  response = OpenID.fetch(url)
54
53
  rescue
55
- raise XRIHTTPError, ["Could not fetch #{xri}", $!]
54
+ raise XRIHTTPError, "Could not fetch #{xri}, #{$!}"
56
55
  end
57
56
  raise XRIHTTPError, "Could not fetch #{xri}" if response.nil?
58
57
 
59
58
  xrds = Yadis::parseXRDS(response.body)
60
59
  canonicalID = Yadis::get_canonical_id(xri, xrds)
61
60
 
62
- Yadis::services(xrds) unless xrds.nil?
63
- }
61
+ return canonicalID, Yadis::services(xrds)
64
62
  # TODO:
65
63
  # * If we do get hits for multiple service_types, we're almost
66
64
  # certainly going to have duplicated service entries and
67
65
  # broken priority ordering.
68
- services = services.inject([]) { |flatter, some_services|
69
- flatter += some_services unless some_services.nil?
70
- }
71
-
72
- return canonicalID, services
73
66
  end
74
67
  end
75
68
 
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html>
3
+ <head>
4
+ <title>Identity Page for Smoker</title>
5
+ <link rel="openid.server" href="http://www.myopenid.com/server" />
6
+ <link rel="openid.delegate" href="http://smoker.myopenid.com/" />
7
+ </head>
8
+ <body>
9
+ <p>こんにちは</p>
10
+ </body>
11
+ </html>
@@ -371,7 +371,28 @@ module OpenID
371
371
  ax_req = FetchRequest.from_openid_request(openid_req)
372
372
  assert(ax_req.nil?)
373
373
  end
374
-
374
+
375
+ def test_from_openid_request_wrong_ax_mode
376
+ uri = 'http://under.the.sea/'
377
+ name = 'ext0'
378
+ value = 'snarfblat'
379
+
380
+ message = OpenID::Message.from_openid_args({
381
+ 'mode' => 'id_res',
382
+ 'ns' => OPENID2_NS,
383
+ 'ns.ax' => AXMessage::NS_URI,
384
+ 'ax.update_url' => 'http://example.com/realm/update_path',
385
+ 'ax.mode' => 'store_request',
386
+ 'ax.type.' + name => uri,
387
+ 'ax.count.' + name => '1',
388
+ 'ax.value.' + name + '.1' => value
389
+ })
390
+ openid_req = Server::OpenIDRequest.new
391
+ openid_req.message = message
392
+ ax_req = FetchRequest.from_openid_request(openid_req)
393
+ assert(ax_req.nil?)
394
+ end
395
+
375
396
  def test_openid_update_url_verification_error
376
397
  openid_req_msg = Message.from_openid_args({
377
398
  'mode' => 'checkid_setup',
@@ -602,7 +623,28 @@ module OpenID
602
623
  }
603
624
  assert_equal(eargs, @msg.get_extension_args)
604
625
  end
605
-
626
+
627
+ def test_from_openid_request_wrong_ax_mode
628
+ uri = 'http://under.the.sea/'
629
+ name = 'ext0'
630
+ value = 'snarfblat'
631
+
632
+ message = OpenID::Message.from_openid_args({
633
+ 'mode' => 'id_res',
634
+ 'ns' => OPENID2_NS,
635
+ 'ns.ax' => AXMessage::NS_URI,
636
+ 'ax.update_url' => 'http://example.com/realm/update_path',
637
+ 'ax.mode' => 'fetch_request',
638
+ 'ax.type.' + name => uri,
639
+ 'ax.count.' + name => '1',
640
+ 'ax.value.' + name + '.1' => value
641
+ })
642
+ openid_req = Server::OpenIDRequest.new
643
+ openid_req.message = message
644
+ ax_req = StoreRequest.from_openid_request(openid_req)
645
+ assert(ax_req.nil?)
646
+ end
647
+
606
648
  def test_get_extension_args_nonempty
607
649
  @msg.set_values(@type_a, ['foo','bar'])
608
650
  aliases = NamespaceMap.new
@@ -369,6 +369,20 @@ module OpenID
369
369
  }
370
370
  end
371
371
 
372
+ def test_html_utf8
373
+ utf8_html = read_data_file('test_discover/openid_utf8.html', false)
374
+ utf8_html.force_encoding("UTF-8") if utf8_html.respond_to?(:force_encoding)
375
+ services = _discover('text/html', utf8_html, 1)
376
+
377
+ _checkService(services[0],
378
+ "http://www.myopenid.com/server",
379
+ @id_url,
380
+ 'http://smoker.myopenid.com/',
381
+ nil,
382
+ ['1.1'],
383
+ false)
384
+ end
385
+
372
386
  def test_yadisEmpty
373
387
  services = _discover('application/xrds+xml',
374
388
  read_data_file('test_discover/yadis_0entries.xml', false),
@@ -1,3 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+
1
3
  require 'test/unit'
2
4
  require 'net/http'
3
5
  require 'webrick'
@@ -112,7 +114,22 @@ class FetcherTestCase < Test::Unit::TestCase
112
114
  assert_block("Fetched too many times.") { @_redirect_counter < 10 }
113
115
  }
114
116
  end
115
-
117
+
118
+ UTF8_PAGE_CONTENT = <<-EOHTML
119
+ <html>
120
+ <head><title>UTF-8</title></head>
121
+ <body>こんにちは</body>
122
+ </html>
123
+ EOHTML
124
+ def _utf8_page
125
+ lambda { |req, resp|
126
+ resp['Content-Type'] = "text/html; charset=utf-8"
127
+ body = UTF8_PAGE_CONTENT.dup
128
+ body.force_encoding("ASCII-8BIT") if body.respond_to?(:force_encoding)
129
+ resp.body = body
130
+ }
131
+ end
132
+
116
133
  def setup
117
134
  @fetcher = OpenID::StandardFetcher.new
118
135
  @logfile = StringIO.new
@@ -138,6 +155,7 @@ class FetcherTestCase < Test::Unit::TestCase
138
155
  }
139
156
  @server.mount_proc('/post', _require_post)
140
157
  @server.mount_proc('/redirect_loop', _redirect_loop)
158
+ @server.mount_proc('/utf8_page', _utf8_page)
141
159
  @server.start
142
160
  }
143
161
  @uri = _uri_build
@@ -211,6 +229,15 @@ class FetcherTestCase < Test::Unit::TestCase
211
229
  }
212
230
  end
213
231
 
232
+ def test_utf8_page
233
+ uri = _uri_build('/utf8_page')
234
+ response = @fetcher.fetch(uri)
235
+ assert_equal(UTF8_PAGE_CONTENT, response.body)
236
+ if response.body.respond_to?(:encoding)
237
+ assert_equal(Encoding::UTF_8, response.body.encoding)
238
+ end
239
+ end
240
+
214
241
  def test_cases
215
242
  for path, expected_code, expected_url in @@cases
216
243
  uri = _uri_build(path)
@@ -61,6 +61,7 @@ module OpenID
61
61
  # test all missing fields for OpenID 1 and 2
62
62
  1.times do
63
63
  [["openid1", OPENID1_NS, OPENID1_FIELDS],
64
+ ["openid1", OPENID11_NS, OPENID1_FIELDS],
64
65
  ["openid2", OPENID2_NS, OPENID2_FIELDS],
65
66
  ].each do |ver, ns, all_fields|
66
67
  all_fields.each do |field|
@@ -81,6 +82,7 @@ module OpenID
81
82
  # Test all missing signed for OpenID 1 and 2
82
83
  1.times do
83
84
  [["openid1", OPENID1_NS, OPENID1_FIELDS, OPENID1_SIGNED],
85
+ ["openid1", OPENID11_NS, OPENID1_FIELDS, OPENID1_SIGNED],
84
86
  ["openid2", OPENID2_NS, OPENID2_FIELDS, OPENID2_SIGNED],
85
87
  ].each do |ver, ns, all_fields, signed_fields|
86
88
  signed_fields.each do |signed_field|
@@ -144,6 +146,14 @@ module OpenID
144
146
  idres.send(:check_for_fields)
145
147
  }
146
148
  end
149
+
150
+ def test_success_openid1_1
151
+ msg = mkMsg(OPENID11_NS, OPENID1_FIELDS, OPENID1_SIGNED)
152
+ idres = IdResHandler.new(msg, nil)
153
+ assert_nothing_raised {
154
+ idres.send(:check_for_fields)
155
+ }
156
+ end
147
157
  end
148
158
 
149
159
  class ReturnToArgsTest < Test::Unit::TestCase
@@ -499,13 +509,23 @@ module OpenID
499
509
  end
500
510
 
501
511
  def test_openid1_success
502
- assert_nothing_raised {
503
- call_check_nonce({'rp_nonce' => @nonce}, true)
504
- }
512
+ [{},
513
+ {'openid.ns' => OPENID1_NS},
514
+ {'openid.ns' => OPENID11_NS}
515
+ ].each do |args|
516
+ assert_nothing_raised {
517
+ call_check_nonce({'rp_nonce' => @nonce}.merge(args), true)
518
+ }
519
+ end
505
520
  end
506
521
 
507
522
  def test_openid1_missing
508
- assert_protocol_error('Nonce missing') { call_check_nonce({}) }
523
+ [{},
524
+ {'openid.ns' => OPENID1_NS},
525
+ {'openid.ns' => OPENID11_NS}
526
+ ].each do |args|
527
+ assert_protocol_error('Nonce missing') { call_check_nonce(args) }
528
+ end
509
529
  end
510
530
 
511
531
  def test_openid2_ignore_rp_nonce
@@ -523,9 +543,14 @@ module OpenID
523
543
  end
524
544
 
525
545
  def test_openid1_ignore_response_nonce
526
- assert_protocol_error('Nonce missing') {
527
- call_check_nonce({'openid.response_nonce' => @nonce})
528
- }
546
+ [{},
547
+ {'openid.ns' => OPENID1_NS},
548
+ {'openid.ns' => OPENID11_NS}
549
+ ].each do |args|
550
+ assert_protocol_error('Nonce missing') {
551
+ call_check_nonce({'openid.response_nonce' => @nonce}.merge(args))
552
+ }
553
+ end
529
554
  end
530
555
 
531
556
  def test_no_store
@@ -587,37 +612,38 @@ module OpenID
587
612
  end
588
613
 
589
614
  def test_openid1_fallback_1_0
590
- claimed_id = 'http://claimed.id/'
591
- @endpoint = nil
592
- resp_mesg = Message.from_openid_args({
593
- 'ns' => OPENID1_NS,
594
- 'identity' => claimed_id,
595
- })
615
+ [OPENID1_NS, OPENID11_NS].each do |openid1_ns|
616
+ claimed_id = 'http://claimed.id/'
617
+ @endpoint = nil
618
+ resp_mesg = Message.from_openid_args({
619
+ 'ns' => openid1_ns,
620
+ 'identity' => claimed_id,
621
+ })
596
622
 
597
- # Pass the OpenID 1 claimed_id this way since we're passing
598
- # None for the endpoint.
599
- resp_mesg.set_arg(BARE_NS, 'openid1_claimed_id', claimed_id)
600
-
601
- # We expect the OpenID 1 discovery verification to try
602
- # matching the discovered endpoint against the 1.1 type and
603
- # fall back to 1.0.
604
- expected_endpoint = OpenIDServiceEndpoint.new
605
- expected_endpoint.type_uris = [OPENID_1_0_TYPE]
606
- expected_endpoint.local_id = nil
607
- expected_endpoint.claimed_id = claimed_id
608
-
609
- hacked_discover = Proc.new {
610
- |_claimed_id| ['unused', [expected_endpoint]]
611
- }
612
- idres = IdResHandler.new(resp_mesg, nil, nil, @endpoint)
613
- assert_log_matches('Performing discovery') {
614
- OpenID.with_method_overridden(:discover, hacked_discover) {
615
- idres.send(:verify_discovery_results)
616
- }
617
- }
618
- actual_endpoint = idres.instance_variable_get(:@endpoint)
619
- assert_equal(actual_endpoint, expected_endpoint)
623
+ # Pass the OpenID 1 claimed_id this way since we're
624
+ # passing None for the endpoint.
625
+ resp_mesg.set_arg(BARE_NS, 'openid1_claimed_id', claimed_id)
626
+
627
+ # We expect the OpenID 1 discovery verification to try
628
+ # matching the discovered endpoint against the 1.1 type
629
+ # and fall back to 1.0.
630
+ expected_endpoint = OpenIDServiceEndpoint.new
631
+ expected_endpoint.type_uris = [OPENID_1_0_TYPE]
632
+ expected_endpoint.local_id = nil
633
+ expected_endpoint.claimed_id = claimed_id
620
634
 
635
+ hacked_discover = Proc.new {
636
+ |_claimed_id| ['unused', [expected_endpoint]]
637
+ }
638
+ idres = IdResHandler.new(resp_mesg, nil, nil, @endpoint)
639
+ assert_log_matches('Performing discovery') {
640
+ OpenID.with_method_overridden(:discover, hacked_discover) {
641
+ idres.send(:verify_discovery_results)
642
+ }
643
+ }
644
+ actual_endpoint = idres.instance_variable_get(:@endpoint)
645
+ assert_equal(actual_endpoint, expected_endpoint)
646
+ end
621
647
  end
622
648
 
623
649
  def test_openid2_no_op_endpoint
@@ -710,6 +736,21 @@ module OpenID
710
736
  assert(e.to_s =~ /different subjects/)
711
737
  end
712
738
 
739
+ def test_openid1_1_verify_discovery_single_no_server_url
740
+ idres = IdResHandler.new(nil, nil)
741
+ @endpoint.local_id = 'my identity'
742
+ @endpoint.claimed_id = 'http://i-am-sam/'
743
+ @endpoint.server_url = 'Phone Home'
744
+ @endpoint.type_uris = [OPENID_1_1_TYPE]
745
+
746
+ to_match = @endpoint.dup
747
+ to_match.claimed_id = 'http://i-am-sam/'
748
+ to_match.type_uris = [OPENID_1_1_TYPE]
749
+ to_match.server_url = nil
750
+
751
+ idres.send(:verify_discovery_single, @endpoint, to_match)
752
+ end
753
+
713
754
  def test_openid2_use_pre_discovered
714
755
  @endpoint.local_id = 'my identity'
715
756
  @endpoint.claimed_id = 'http://i-am-sam/'