httpclient 2.3.0.1 → 2.8.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +85 -0
- data/bin/httpclient +18 -6
- data/bin/jsonclient +85 -0
- data/lib/http-access2.rb +1 -1
- data/lib/httpclient.rb +262 -88
- data/lib/httpclient/auth.rb +269 -244
- data/lib/httpclient/cacert.pem +3952 -0
- data/lib/httpclient/cacert1024.pem +3866 -0
- data/lib/httpclient/connection.rb +1 -1
- data/lib/httpclient/cookie.rb +161 -514
- data/lib/httpclient/http.rb +57 -21
- data/lib/httpclient/include_client.rb +2 -0
- data/lib/httpclient/jruby_ssl_socket.rb +588 -0
- data/lib/httpclient/session.rb +259 -317
- data/lib/httpclient/ssl_config.rb +141 -188
- data/lib/httpclient/ssl_socket.rb +150 -0
- data/lib/httpclient/timeout.rb +1 -1
- data/lib/httpclient/util.rb +62 -1
- data/lib/httpclient/version.rb +1 -1
- data/lib/httpclient/webagent-cookie.rb +459 -0
- data/lib/jsonclient.rb +63 -0
- data/lib/oauthclient.rb +2 -1
- data/sample/jsonclient.rb +67 -0
- data/sample/oauth_twitter.rb +4 -4
- data/test/{ca-chain.cert → ca-chain.pem} +0 -0
- data/test/client-pass.key +18 -0
- data/test/helper.rb +10 -8
- data/test/jruby_ssl_socket/test_pemutils.rb +32 -0
- data/test/test_auth.rb +175 -4
- data/test/test_cookie.rb +147 -243
- data/test/test_http-access2.rb +17 -16
- data/test/test_httpclient.rb +458 -77
- data/test/test_jsonclient.rb +80 -0
- data/test/test_ssl.rb +341 -17
- data/test/test_webagent-cookie.rb +465 -0
- metadata +57 -55
- data/README.txt +0 -721
- data/lib/httpclient/cacert.p7s +0 -1858
- data/lib/httpclient/cacert_sha1.p7s +0 -1858
- data/sample/oauth_salesforce_10.rb +0 -63
data/lib/httpclient/auth.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# HTTPClient - HTTP client library.
|
2
|
-
# Copyright (C) 2000-
|
2
|
+
# Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
|
3
3
|
#
|
4
4
|
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
|
5
5
|
# redistribute it and/or modify it under the same terms of Ruby's license;
|
@@ -8,31 +8,14 @@
|
|
8
8
|
|
9
9
|
require 'digest/md5'
|
10
10
|
require 'httpclient/session'
|
11
|
+
require 'mutex_m'
|
11
12
|
|
12
13
|
|
13
14
|
class HTTPClient
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
rescue LoadError
|
19
|
-
NTLMEnabled = false
|
20
|
-
end
|
21
|
-
|
22
|
-
begin
|
23
|
-
require 'win32/sspi'
|
24
|
-
SSPIEnabled = true
|
25
|
-
rescue LoadError
|
26
|
-
SSPIEnabled = false
|
27
|
-
end
|
28
|
-
|
29
|
-
begin
|
30
|
-
require 'gssapi'
|
31
|
-
GSSAPIEnabled = true
|
32
|
-
rescue LoadError
|
33
|
-
GSSAPIEnabled = false
|
34
|
-
end
|
35
|
-
|
16
|
+
NTLMEnabled = false
|
17
|
+
SSPIEnabled = false
|
18
|
+
GSSAPIEnabled = false
|
36
19
|
|
37
20
|
# Common abstract class for authentication filter.
|
38
21
|
#
|
@@ -112,6 +95,13 @@ class HTTPClient
|
|
112
95
|
@authenticator.each do |auth|
|
113
96
|
next unless auth.set? # hasn't be set, don't use it
|
114
97
|
if cred = auth.get(req)
|
98
|
+
if cred == :skip
|
99
|
+
# some authenticator (NTLM and Negotiate) does not
|
100
|
+
# need to send extra header after authorization. In such case
|
101
|
+
# it should block other authenticators to respond and :skip is
|
102
|
+
# the marker for such case.
|
103
|
+
return
|
104
|
+
end
|
115
105
|
req.header.set('Authorization', auth.scheme + " " + cred)
|
116
106
|
return
|
117
107
|
end
|
@@ -196,6 +186,13 @@ class HTTPClient
|
|
196
186
|
@authenticator.each do |auth|
|
197
187
|
next unless auth.set? # hasn't be set, don't use it
|
198
188
|
if cred = auth.get(req)
|
189
|
+
if cred == :skip
|
190
|
+
# some authenticator (NTLM and Negotiate) does not
|
191
|
+
# need to send extra header after authorization. In such case
|
192
|
+
# it should block other authenticators to respond and :skip is
|
193
|
+
# the marker for such case.
|
194
|
+
return
|
195
|
+
end
|
199
196
|
req.header.set('Proxy-Authorization', auth.scheme + " " + cred)
|
200
197
|
return
|
201
198
|
end
|
@@ -225,44 +222,59 @@ class HTTPClient
|
|
225
222
|
end
|
226
223
|
end
|
227
224
|
|
228
|
-
# Authentication filter
|
229
|
-
|
230
|
-
class BasicAuth
|
225
|
+
# Authentication filter base class.
|
226
|
+
class AuthBase
|
231
227
|
include HTTPClient::Util
|
232
228
|
|
233
229
|
# Authentication scheme.
|
234
230
|
attr_reader :scheme
|
235
231
|
|
236
|
-
|
237
|
-
|
238
|
-
@
|
239
|
-
@set = false
|
240
|
-
@auth = {}
|
241
|
-
@challengeable = {}
|
242
|
-
@scheme = "Basic"
|
232
|
+
def initialize(scheme)
|
233
|
+
@scheme = scheme
|
234
|
+
@challenge = {}
|
243
235
|
end
|
244
236
|
|
245
237
|
# Resets challenge state. Do not send '*Authorization' header until the
|
246
238
|
# server sends '*Authentication' again.
|
247
239
|
def reset_challenge
|
248
|
-
|
240
|
+
synchronize do
|
241
|
+
@challenge.clear
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Authentication filter for handling BasicAuth negotiation.
|
247
|
+
# Used in WWWAuth and ProxyAuth.
|
248
|
+
class BasicAuth < AuthBase
|
249
|
+
include Mutex_m
|
250
|
+
|
251
|
+
# Send Authorization Header without receiving 401
|
252
|
+
attr_accessor :force_auth
|
253
|
+
|
254
|
+
# Creates new BasicAuth filter.
|
255
|
+
def initialize
|
256
|
+
super('Basic')
|
257
|
+
@cred = nil
|
258
|
+
@auth = {}
|
259
|
+
@force_auth = false
|
249
260
|
end
|
250
261
|
|
251
262
|
# Set authentication credential.
|
252
263
|
# uri == nil for generic purpose (allow to use user/password for any URL).
|
253
264
|
def set(uri, user, passwd)
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
265
|
+
synchronize do
|
266
|
+
if uri.nil?
|
267
|
+
@cred = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
|
268
|
+
else
|
269
|
+
uri = Util.uri_dirname(uri)
|
270
|
+
@auth[uri] = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
|
271
|
+
end
|
260
272
|
end
|
261
273
|
end
|
262
274
|
|
263
275
|
# have we marked this as set - ie that it's valid to use in this context?
|
264
276
|
def set?
|
265
|
-
@
|
277
|
+
@cred || @auth.any?
|
266
278
|
end
|
267
279
|
|
268
280
|
# Response handler: returns credential.
|
@@ -271,76 +283,75 @@ class HTTPClient
|
|
271
283
|
# * child page of defined credential
|
272
284
|
def get(req)
|
273
285
|
target_uri = req.header.request_uri
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
Util.
|
286
|
+
synchronize {
|
287
|
+
return nil if !@force_auth and !@challenge.any? { |uri, ok|
|
288
|
+
Util.uri_part_of(target_uri, uri) and ok
|
289
|
+
}
|
290
|
+
return @cred if @cred
|
291
|
+
Util.hash_find_value(@auth) { |uri, cred|
|
292
|
+
Util.uri_part_of(target_uri, uri)
|
293
|
+
}
|
280
294
|
}
|
281
295
|
end
|
282
296
|
|
283
297
|
# Challenge handler: remember URL for response.
|
284
298
|
def challenge(uri, param_str = nil)
|
285
|
-
|
286
|
-
|
299
|
+
synchronize {
|
300
|
+
@challenge[urify(uri)] = true
|
301
|
+
true
|
302
|
+
}
|
287
303
|
end
|
288
304
|
end
|
289
305
|
|
290
306
|
class ProxyBasicAuth < BasicAuth
|
291
|
-
|
292
307
|
def set(uri, user, passwd)
|
293
|
-
|
294
|
-
|
308
|
+
synchronize do
|
309
|
+
@cred = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
|
310
|
+
end
|
295
311
|
end
|
296
312
|
|
297
313
|
def get(req)
|
298
|
-
|
299
|
-
|
300
|
-
|
314
|
+
synchronize {
|
315
|
+
return nil if !@force_auth and !@challenge['challenged']
|
316
|
+
@cred
|
317
|
+
}
|
301
318
|
end
|
302
319
|
|
303
320
|
# Challenge handler: remember URL for response.
|
304
321
|
def challenge(uri, param_str = nil)
|
305
|
-
|
306
|
-
|
322
|
+
synchronize {
|
323
|
+
@challenge['challenged'] = true
|
324
|
+
true
|
325
|
+
}
|
307
326
|
end
|
308
327
|
end
|
309
328
|
|
310
329
|
# Authentication filter for handling DigestAuth negotiation.
|
311
330
|
# Used in WWWAuth.
|
312
|
-
class DigestAuth
|
313
|
-
|
314
|
-
attr_reader :scheme
|
331
|
+
class DigestAuth < AuthBase
|
332
|
+
include Mutex_m
|
315
333
|
|
316
334
|
# Creates new DigestAuth filter.
|
317
335
|
def initialize
|
336
|
+
super('Digest')
|
318
337
|
@auth = {}
|
319
|
-
@challenge = {}
|
320
|
-
@set = false
|
321
338
|
@nonce_count = 0
|
322
|
-
@scheme = "Digest"
|
323
|
-
end
|
324
|
-
|
325
|
-
# Resets challenge state. Do not send '*Authorization' header until the
|
326
|
-
# server sends '*Authentication' again.
|
327
|
-
def reset_challenge
|
328
|
-
@challenge.clear
|
329
339
|
end
|
330
340
|
|
331
341
|
# Set authentication credential.
|
332
342
|
# uri == nil is ignored.
|
333
343
|
def set(uri, user, passwd)
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
344
|
+
synchronize do
|
345
|
+
if uri
|
346
|
+
uri = Util.uri_dirname(uri)
|
347
|
+
@auth[uri] = [user, passwd]
|
348
|
+
end
|
338
349
|
end
|
339
350
|
end
|
340
351
|
|
341
352
|
# have we marked this as set - ie that it's valid to use in this context?
|
342
353
|
def set?
|
343
|
-
@
|
354
|
+
@auth.any?
|
344
355
|
end
|
345
356
|
|
346
357
|
# Response handler: returns credential.
|
@@ -349,21 +360,25 @@ class HTTPClient
|
|
349
360
|
# * child page of defined credential
|
350
361
|
def get(req)
|
351
362
|
target_uri = req.header.request_uri
|
352
|
-
|
353
|
-
Util.
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
Util.
|
363
|
+
synchronize {
|
364
|
+
param = Util.hash_find_value(@challenge) { |uri, v|
|
365
|
+
Util.uri_part_of(target_uri, uri)
|
366
|
+
}
|
367
|
+
return nil unless param
|
368
|
+
user, passwd = Util.hash_find_value(@auth) { |uri, auth_data|
|
369
|
+
Util.uri_part_of(target_uri, uri)
|
370
|
+
}
|
371
|
+
return nil unless user
|
372
|
+
calc_cred(req, user, passwd, param)
|
358
373
|
}
|
359
|
-
return nil unless user
|
360
|
-
calc_cred(req, user, passwd, param)
|
361
374
|
end
|
362
375
|
|
363
376
|
# Challenge handler: remember URL and challenge token for response.
|
364
377
|
def challenge(uri, param_str)
|
365
|
-
|
366
|
-
|
378
|
+
synchronize {
|
379
|
+
@challenge[uri] = parse_challenge_param(param_str)
|
380
|
+
true
|
381
|
+
}
|
367
382
|
end
|
368
383
|
|
369
384
|
private
|
@@ -445,127 +460,131 @@ class HTTPClient
|
|
445
460
|
|
446
461
|
# overrides DigestAuth#set. sets default user name and password. uri is not used.
|
447
462
|
def set(uri, user, passwd)
|
448
|
-
|
449
|
-
|
463
|
+
synchronize do
|
464
|
+
@auth = [user, passwd]
|
465
|
+
end
|
450
466
|
end
|
451
467
|
|
452
468
|
# overrides DigestAuth#get. Uses default user name and password
|
453
469
|
# regardless of target uri if the proxy has required authentication
|
454
470
|
# before
|
455
471
|
def get(req)
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
472
|
+
synchronize {
|
473
|
+
param = @challenge
|
474
|
+
return nil unless param
|
475
|
+
user, passwd = @auth
|
476
|
+
return nil unless user
|
477
|
+
calc_cred(req, user, passwd, param)
|
478
|
+
}
|
462
479
|
end
|
463
480
|
|
464
481
|
def reset_challenge
|
465
|
-
|
482
|
+
synchronize do
|
483
|
+
@challenge = nil
|
484
|
+
end
|
466
485
|
end
|
467
486
|
|
468
487
|
def challenge(uri, param_str)
|
469
|
-
|
470
|
-
|
488
|
+
synchronize {
|
489
|
+
@challenge = parse_challenge_param(param_str)
|
490
|
+
true
|
491
|
+
}
|
471
492
|
end
|
472
|
-
|
473
493
|
end
|
474
494
|
|
475
495
|
# Authentication filter for handling Negotiate/NTLM negotiation.
|
476
496
|
# Used in WWWAuth and ProxyAuth.
|
477
497
|
#
|
478
498
|
# NegotiateAuth depends on 'ruby/ntlm' module.
|
479
|
-
class NegotiateAuth
|
480
|
-
|
481
|
-
|
499
|
+
class NegotiateAuth < AuthBase
|
500
|
+
include Mutex_m
|
501
|
+
|
482
502
|
# NTLM opt for ruby/ntlm. {:ntlmv2 => true} by default.
|
483
503
|
attr_reader :ntlm_opt
|
484
504
|
|
485
505
|
# Creates new NegotiateAuth filter.
|
486
506
|
def initialize(scheme = "Negotiate")
|
507
|
+
super(scheme)
|
487
508
|
@auth = {}
|
488
509
|
@auth_default = nil
|
489
|
-
@challenge = {}
|
490
|
-
@scheme = scheme
|
491
|
-
@set = false
|
492
510
|
@ntlm_opt = {
|
493
511
|
:ntlmv2 => true
|
494
512
|
}
|
495
513
|
end
|
496
514
|
|
497
|
-
# Resets challenge state. Do not send '*Authorization' header until the
|
498
|
-
# server sends '*Authentication' again.
|
499
|
-
def reset_challenge
|
500
|
-
@challenge.clear
|
501
|
-
end
|
502
|
-
|
503
515
|
# Set authentication credential.
|
504
516
|
# uri == nil for generic purpose (allow to use user/password for any URL).
|
505
517
|
def set(uri, user, passwd)
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
518
|
+
synchronize do
|
519
|
+
if uri
|
520
|
+
uri = Util.uri_dirname(uri)
|
521
|
+
@auth[uri] = [user, passwd]
|
522
|
+
else
|
523
|
+
@auth_default = [user, passwd]
|
524
|
+
end
|
512
525
|
end
|
513
526
|
end
|
514
527
|
|
515
528
|
# have we marked this as set - ie that it's valid to use in this context?
|
516
529
|
def set?
|
517
|
-
@
|
530
|
+
@auth_default || @auth.any?
|
518
531
|
end
|
519
532
|
|
520
533
|
# Response handler: returns credential.
|
521
534
|
# See ruby/ntlm for negotiation state transition.
|
522
535
|
def get(req)
|
523
|
-
return nil unless NTLMEnabled
|
524
536
|
target_uri = req.header.request_uri
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
Util.
|
537
|
+
synchronize {
|
538
|
+
_domain_uri, param = @challenge.find { |uri, v|
|
539
|
+
Util.uri_part_of(target_uri, uri)
|
540
|
+
}
|
541
|
+
return nil unless param
|
542
|
+
user, passwd = Util.hash_find_value(@auth) { |uri, auth_data|
|
543
|
+
Util.uri_part_of(target_uri, uri)
|
544
|
+
}
|
545
|
+
unless user
|
546
|
+
user, passwd = @auth_default
|
547
|
+
end
|
548
|
+
return nil unless user
|
549
|
+
Util.try_require('net/ntlm') || return
|
550
|
+
domain = nil
|
551
|
+
domain, user = user.split("\\") if user.index("\\")
|
552
|
+
state = param[:state]
|
553
|
+
authphrase = param[:authphrase]
|
554
|
+
case state
|
555
|
+
when :init
|
556
|
+
t1 = Net::NTLM::Message::Type1.new
|
557
|
+
t1.domain = domain if domain
|
558
|
+
t1.encode64
|
559
|
+
when :response
|
560
|
+
t2 = Net::NTLM::Message.decode64(authphrase)
|
561
|
+
param = {:user => user, :password => passwd}
|
562
|
+
param[:domain] = domain if domain
|
563
|
+
t3 = t2.response(param, @ntlm_opt.dup)
|
564
|
+
@challenge[target_uri][:state] = :done
|
565
|
+
t3.encode64
|
566
|
+
when :done
|
567
|
+
:skip
|
568
|
+
else
|
569
|
+
nil
|
570
|
+
end
|
531
571
|
}
|
532
|
-
unless user
|
533
|
-
user, passwd = @auth_default
|
534
|
-
end
|
535
|
-
return nil unless user
|
536
|
-
domain = nil
|
537
|
-
domain, user = user.split("\\") if user.index("\\")
|
538
|
-
state = param[:state]
|
539
|
-
authphrase = param[:authphrase]
|
540
|
-
case state
|
541
|
-
when :init
|
542
|
-
t1 = Net::NTLM::Message::Type1.new
|
543
|
-
t1.domain = domain if domain
|
544
|
-
return t1.encode64
|
545
|
-
when :response
|
546
|
-
t2 = Net::NTLM::Message.decode64(authphrase)
|
547
|
-
param = {:user => user, :password => passwd}
|
548
|
-
param[:domain] = domain if domain
|
549
|
-
t3 = t2.response(param, @ntlm_opt.dup)
|
550
|
-
@challenge.delete(domain_uri)
|
551
|
-
return t3.encode64
|
552
|
-
end
|
553
|
-
nil
|
554
572
|
end
|
555
573
|
|
556
574
|
# Challenge handler: remember URL and challenge token for response.
|
557
575
|
def challenge(uri, param_str)
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
576
|
+
synchronize {
|
577
|
+
if param_str.nil? or @challenge[uri].nil?
|
578
|
+
c = @challenge[uri] = {}
|
579
|
+
c[:state] = :init
|
580
|
+
c[:authphrase] = ""
|
581
|
+
else
|
582
|
+
c = @challenge[uri]
|
583
|
+
c[:state] = :response
|
584
|
+
c[:authphrase] = param_str
|
585
|
+
end
|
586
|
+
true
|
587
|
+
}
|
569
588
|
end
|
570
589
|
end
|
571
590
|
|
@@ -574,20 +593,12 @@ class HTTPClient
|
|
574
593
|
# Used in ProxyAuth.
|
575
594
|
#
|
576
595
|
# SSPINegotiateAuth depends on 'win32/sspi' module.
|
577
|
-
class SSPINegotiateAuth
|
578
|
-
|
579
|
-
attr_reader :scheme
|
596
|
+
class SSPINegotiateAuth < AuthBase
|
597
|
+
include Mutex_m
|
580
598
|
|
581
599
|
# Creates new SSPINegotiateAuth filter.
|
582
600
|
def initialize
|
583
|
-
|
584
|
-
@scheme = "Negotiate"
|
585
|
-
end
|
586
|
-
|
587
|
-
# Resets challenge state. Do not send '*Authorization' header until the
|
588
|
-
# server sends '*Authentication' again.
|
589
|
-
def reset_challenge
|
590
|
-
@challenge.clear
|
601
|
+
super('Negotiate')
|
591
602
|
end
|
592
603
|
|
593
604
|
# Set authentication credential.
|
@@ -597,58 +608,64 @@ class HTTPClient
|
|
597
608
|
# not supported
|
598
609
|
end
|
599
610
|
|
600
|
-
#
|
611
|
+
# Check always (not effective but it works)
|
601
612
|
def set?
|
602
|
-
|
613
|
+
!@challenge.empty?
|
603
614
|
end
|
604
615
|
|
605
616
|
# Response handler: returns credential.
|
606
617
|
# See win32/sspi for negotiation state transition.
|
607
618
|
def get(req)
|
608
|
-
return nil unless SSPIEnabled || GSSAPIEnabled
|
609
619
|
target_uri = req.header.request_uri
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
#
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
620
|
+
synchronize {
|
621
|
+
domain_uri, param = @challenge.find { |uri, v|
|
622
|
+
Util.uri_part_of(target_uri, uri)
|
623
|
+
}
|
624
|
+
return nil unless param
|
625
|
+
Util.try_require('win32/sspi') || Util.try_require('gssapi') || return
|
626
|
+
state = param[:state]
|
627
|
+
authenticator = param[:authenticator]
|
628
|
+
authphrase = param[:authphrase]
|
629
|
+
case state
|
630
|
+
when :init
|
631
|
+
if defined?(Win32::SSPI)
|
632
|
+
authenticator = param[:authenticator] = Win32::SSPI::NegotiateAuth.new
|
633
|
+
authenticator.get_initial_token(@scheme)
|
634
|
+
else # use GSSAPI
|
635
|
+
authenticator = param[:authenticator] = GSSAPI::Simple.new(domain_uri.host, 'HTTP')
|
636
|
+
# Base64 encode the context token
|
637
|
+
[authenticator.init_context].pack('m').gsub(/\n/,'')
|
638
|
+
end
|
639
|
+
when :response
|
640
|
+
@challenge[target_uri][:state] = :done
|
641
|
+
if defined?(Win32::SSPI)
|
642
|
+
authenticator.complete_authentication(authphrase)
|
643
|
+
else # use GSSAPI
|
644
|
+
authenticator.init_context(authphrase.unpack('m').pop)
|
645
|
+
end
|
646
|
+
when :done
|
647
|
+
:skip
|
648
|
+
else
|
649
|
+
nil
|
633
650
|
end
|
634
|
-
|
635
|
-
nil
|
651
|
+
}
|
636
652
|
end
|
637
653
|
|
638
654
|
# Challenge handler: remember URL and challenge token for response.
|
639
655
|
def challenge(uri, param_str)
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
656
|
+
synchronize {
|
657
|
+
if param_str.nil? or @challenge[uri].nil?
|
658
|
+
c = @challenge[uri] = {}
|
659
|
+
c[:state] = :init
|
660
|
+
c[:authenticator] = nil
|
661
|
+
c[:authphrase] = ""
|
662
|
+
else
|
663
|
+
c = @challenge[uri]
|
664
|
+
c[:state] = :response
|
665
|
+
c[:authphrase] = param_str
|
666
|
+
end
|
667
|
+
true
|
668
|
+
}
|
652
669
|
end
|
653
670
|
end
|
654
671
|
|
@@ -662,11 +679,8 @@ class HTTPClient
|
|
662
679
|
# CAUTION: This impl does NOT support OAuth Request Body Hash spec for now.
|
663
680
|
# http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
|
664
681
|
#
|
665
|
-
class OAuth
|
666
|
-
include
|
667
|
-
|
668
|
-
# Authentication scheme.
|
669
|
-
attr_reader :scheme
|
682
|
+
class OAuth < AuthBase
|
683
|
+
include Mutex_m
|
670
684
|
|
671
685
|
class Config
|
672
686
|
include HTTPClient::Util
|
@@ -737,20 +751,13 @@ class HTTPClient
|
|
737
751
|
|
738
752
|
# Creates new DigestAuth filter.
|
739
753
|
def initialize
|
754
|
+
super('OAuth')
|
740
755
|
@config = nil # common config
|
741
756
|
@auth = {} # configs for each site
|
742
|
-
@challengeable = {}
|
743
757
|
@nonce_count = 0
|
744
758
|
@signature_handler = {
|
745
759
|
'HMAC-SHA1' => method(:sign_hmac_sha1)
|
746
760
|
}
|
747
|
-
@scheme = "OAuth"
|
748
|
-
end
|
749
|
-
|
750
|
-
# Resets challenge state. Do not send '*Authorization' header until the
|
751
|
-
# server sends '*Authentication' again.
|
752
|
-
def reset_challenge
|
753
|
-
@challengeable.clear
|
754
761
|
end
|
755
762
|
|
756
763
|
# Set authentication credential.
|
@@ -759,31 +766,28 @@ class HTTPClient
|
|
759
766
|
# not supported
|
760
767
|
end
|
761
768
|
|
762
|
-
#
|
769
|
+
# Check always (not effective but it works)
|
763
770
|
def set?
|
764
|
-
|
771
|
+
!@challenge.empty?
|
765
772
|
end
|
766
773
|
|
767
774
|
# Set authentication credential.
|
768
775
|
def set_config(uri, config)
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
776
|
+
synchronize do
|
777
|
+
if uri.nil?
|
778
|
+
@config = config
|
779
|
+
else
|
780
|
+
uri = Util.uri_dirname(urify(uri))
|
781
|
+
@auth[uri] = config
|
782
|
+
end
|
774
783
|
end
|
775
784
|
end
|
776
785
|
|
777
786
|
# Get authentication credential.
|
778
787
|
def get_config(uri = nil)
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
uri = urify(uri)
|
783
|
-
Util.hash_find_value(@auth) { |cand_uri, cred|
|
784
|
-
Util.uri_part_of(uri, cand_uri)
|
785
|
-
}
|
786
|
-
end
|
788
|
+
synchronize {
|
789
|
+
do_get_config(uri)
|
790
|
+
}
|
787
791
|
end
|
788
792
|
|
789
793
|
# Response handler: returns credential.
|
@@ -792,33 +796,54 @@ class HTTPClient
|
|
792
796
|
# * child page of defined credential
|
793
797
|
def get(req)
|
794
798
|
target_uri = req.header.request_uri
|
795
|
-
|
796
|
-
|
799
|
+
synchronize {
|
800
|
+
return nil unless @challenge[nil] or @challenge.find { |uri, ok|
|
801
|
+
Util.uri_part_of(target_uri, uri) and ok
|
802
|
+
}
|
803
|
+
config = do_get_config(target_uri) || @config
|
804
|
+
return nil unless config
|
805
|
+
calc_cred(req, config)
|
797
806
|
}
|
798
|
-
config = get_config(target_uri) || @config
|
799
|
-
return nil unless config
|
800
|
-
calc_cred(req, config)
|
801
807
|
end
|
802
808
|
|
803
809
|
# Challenge handler: remember URL for response.
|
810
|
+
#
|
811
|
+
# challenge() in OAuth handler always returns false to avoid connection
|
812
|
+
# retry which should not work in OAuth authentication context. This
|
813
|
+
# method just remember URL (nil means 'any') for the next connection.
|
814
|
+
# Normally OAuthClient handles this correctly but see how it uses when
|
815
|
+
# you need to use this class directly.
|
804
816
|
def challenge(uri, param_str = nil)
|
817
|
+
synchronize {
|
818
|
+
if uri.nil?
|
819
|
+
@challenge[nil] = true
|
820
|
+
else
|
821
|
+
@challenge[urify(uri)] = true
|
822
|
+
end
|
823
|
+
false
|
824
|
+
}
|
825
|
+
end
|
826
|
+
|
827
|
+
private
|
828
|
+
|
829
|
+
def do_get_config(uri = nil)
|
805
830
|
if uri.nil?
|
806
|
-
@
|
831
|
+
@config
|
807
832
|
else
|
808
|
-
|
833
|
+
uri = urify(uri)
|
834
|
+
Util.hash_find_value(@auth) { |cand_uri, cred|
|
835
|
+
Util.uri_part_of(uri, cand_uri)
|
836
|
+
}
|
809
837
|
end
|
810
|
-
true
|
811
838
|
end
|
812
839
|
|
813
|
-
private
|
814
|
-
|
815
840
|
def calc_cred(req, config)
|
816
841
|
header = {}
|
817
842
|
header['oauth_consumer_key'] = config.consumer_key
|
818
|
-
header['oauth_token'] = config.token
|
819
843
|
header['oauth_signature_method'] = config.signature_method
|
820
844
|
header['oauth_timestamp'] = config.debug_timestamp || Time.now.to_i.to_s
|
821
845
|
header['oauth_nonce'] = config.debug_nonce || generate_nonce()
|
846
|
+
header['oauth_token'] = config.token if config.token
|
822
847
|
header['oauth_version'] = config.version if config.version
|
823
848
|
header['oauth_callback'] = config.callback if config.callback
|
824
849
|
header['oauth_verifier'] = config.verifier if config.verifier
|