httpclient-xaop 2.1.6

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.
@@ -0,0 +1,762 @@
1
+ # HTTPClient - HTTP client library.
2
+ # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
+ #
4
+ # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
+ # redistribute it and/or modify it under the same terms of Ruby's license;
6
+ # either the dual license version in 2003, or any later version.
7
+
8
+
9
+ require 'digest/md5'
10
+ require 'httpclient/session'
11
+
12
+
13
+ class HTTPClient
14
+
15
+ begin
16
+ require 'net/ntlm'
17
+ NTLMEnabled = true
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
+
30
+ # Common abstract class for authentication filter.
31
+ #
32
+ # There are 2 authentication filters.
33
+ # WWWAuth:: Authentication filter for handling authentication negotiation
34
+ # between Web server. Parses 'WWW-Authentication' header in
35
+ # response and generates 'Authorization' header in request.
36
+ # ProxyAuth:: Authentication filter for handling authentication negotiation
37
+ # between Proxy server. Parses 'Proxy-Authentication' header in
38
+ # response and generates 'Proxy-Authorization' header in request.
39
+ class AuthFilterBase
40
+ private
41
+
42
+ def parse_authentication_header(res, tag)
43
+ challenge = res.header[tag]
44
+ return nil unless challenge
45
+ challenge.collect { |c| parse_challenge_header(c) }.compact
46
+ end
47
+
48
+ def parse_challenge_header(challenge)
49
+ scheme, param_str = challenge.scan(/\A(\S+)(?:\s+(.*))?\z/)[0]
50
+ return nil if scheme.nil?
51
+ return scheme, param_str
52
+ end
53
+ end
54
+
55
+
56
+ # Authentication filter for handling authentication negotiation between
57
+ # Web server. Parses 'WWW-Authentication' header in response and
58
+ # generates 'Authorization' header in request.
59
+ #
60
+ # Authentication filter is implemented using request filter of HTTPClient.
61
+ # It traps HTTP response header and maintains authentication state, and
62
+ # traps HTTP request header for inserting necessary authentication header.
63
+ #
64
+ # WWWAuth has sub filters (BasicAuth, DigestAuth, NegotiateAuth and
65
+ # SSPINegotiateAuth) and delegates some operations to it.
66
+ # NegotiateAuth requires 'ruby/ntlm' module.
67
+ # SSPINegotiateAuth requires 'win32/sspi' module.
68
+ class WWWAuth < AuthFilterBase
69
+ attr_reader :basic_auth
70
+ attr_reader :digest_auth
71
+ attr_reader :negotiate_auth
72
+ attr_reader :sspi_negotiate_auth
73
+ attr_reader :oauth
74
+
75
+ # Creates new WWWAuth.
76
+ def initialize
77
+ @basic_auth = BasicAuth.new
78
+ @digest_auth = DigestAuth.new
79
+ @negotiate_auth = NegotiateAuth.new
80
+ @ntlm_auth = NegotiateAuth.new('NTLM')
81
+ @sspi_negotiate_auth = SSPINegotiateAuth.new
82
+ @oauth = OAuth.new
83
+ # sort authenticators by priority
84
+ @authenticator = [@oauth, @negotiate_auth, @ntlm_auth, @sspi_negotiate_auth, @digest_auth, @basic_auth]
85
+ end
86
+
87
+ # Resets challenge state. See sub filters for more details.
88
+ def reset_challenge
89
+ @authenticator.each do |auth|
90
+ auth.reset_challenge
91
+ end
92
+ end
93
+
94
+ # Set authentication credential. See sub filters for more details.
95
+ def set_auth(uri, user, passwd)
96
+ @authenticator.each do |auth|
97
+ auth.set(uri, user, passwd)
98
+ end
99
+ reset_challenge
100
+ end
101
+
102
+ # Filter API implementation. Traps HTTP request and insert
103
+ # 'Authorization' header if needed.
104
+ def filter_request(req)
105
+ @authenticator.each do |auth|
106
+ if cred = auth.get(req)
107
+ req.header.set('Authorization', auth.scheme + " " + cred)
108
+ return
109
+ end
110
+ end
111
+ end
112
+
113
+ # Filter API implementation. Traps HTTP response and parses
114
+ # 'WWW-Authenticate' header.
115
+ def filter_response(req, res)
116
+ command = nil
117
+ if res.status == HTTP::Status::UNAUTHORIZED
118
+ if challenge = parse_authentication_header(res, 'www-authenticate')
119
+ uri = req.header.request_uri
120
+ challenge.each do |scheme, param_str|
121
+ @authenticator.each do |auth|
122
+ if scheme.downcase == auth.scheme.downcase
123
+ challengeable = auth.challenge(uri, param_str)
124
+ command = :retry if challengeable
125
+ end
126
+ end
127
+ end
128
+ # ignore unknown authentication scheme
129
+ end
130
+ end
131
+ command
132
+ end
133
+ end
134
+
135
+
136
+ # Authentication filter for handling authentication negotiation between
137
+ # Proxy server. Parses 'Proxy-Authentication' header in response and
138
+ # generates 'Proxy-Authorization' header in request.
139
+ #
140
+ # Authentication filter is implemented using request filter of HTTPClient.
141
+ # It traps HTTP response header and maintains authentication state, and
142
+ # traps HTTP request header for inserting necessary authentication header.
143
+ #
144
+ # ProxyAuth has sub filters (BasicAuth, NegotiateAuth, and SSPINegotiateAuth)
145
+ # and delegates some operations to it.
146
+ # NegotiateAuth requires 'ruby/ntlm' module.
147
+ # SSPINegotiateAuth requires 'win32/sspi' module.
148
+ class ProxyAuth < AuthFilterBase
149
+ attr_reader :basic_auth
150
+ attr_reader :negotiate_auth
151
+ attr_reader :sspi_negotiate_auth
152
+
153
+ # Creates new ProxyAuth.
154
+ def initialize
155
+ @basic_auth = BasicAuth.new
156
+ @negotiate_auth = NegotiateAuth.new
157
+ @ntlm_auth = NegotiateAuth.new('NTLM')
158
+ @sspi_negotiate_auth = SSPINegotiateAuth.new
159
+ # sort authenticators by priority
160
+ @authenticator = [@negotiate_auth, @ntlm_auth, @sspi_negotiate_auth, @basic_auth]
161
+ end
162
+
163
+ # Resets challenge state. See sub filters for more details.
164
+ def reset_challenge
165
+ @authenticator.each do |auth|
166
+ auth.reset_challenge
167
+ end
168
+ end
169
+
170
+ # Set authentication credential. See sub filters for more details.
171
+ def set_auth(user, passwd)
172
+ @authenticator.each do |auth|
173
+ auth.set(nil, user, passwd)
174
+ end
175
+ reset_challenge
176
+ end
177
+
178
+ # Filter API implementation. Traps HTTP request and insert
179
+ # 'Proxy-Authorization' header if needed.
180
+ def filter_request(req)
181
+ @authenticator.each do |auth|
182
+ if cred = auth.get(req)
183
+ req.header.set('Proxy-Authorization', auth.scheme + " " + cred)
184
+ return
185
+ end
186
+ end
187
+ end
188
+
189
+ # Filter API implementation. Traps HTTP response and parses
190
+ # 'Proxy-Authenticate' header.
191
+ def filter_response(req, res)
192
+ command = nil
193
+ if res.status == HTTP::Status::PROXY_AUTHENTICATE_REQUIRED
194
+ if challenge = parse_authentication_header(res, 'proxy-authenticate')
195
+ uri = req.header.request_uri
196
+ challenge.each do |scheme, param_str|
197
+ @authenticator.each do |auth|
198
+ if scheme.downcase == auth.scheme.downcase
199
+ challengeable = auth.challenge(uri, param_str)
200
+ command = :retry if challengeable
201
+ end
202
+ end
203
+ end
204
+ # ignore unknown authentication scheme
205
+ end
206
+ end
207
+ command
208
+ end
209
+ end
210
+
211
+ # Authentication filter for handling BasicAuth negotiation.
212
+ # Used in WWWAuth and ProxyAuth.
213
+ class BasicAuth
214
+ include HTTPClient::Util
215
+
216
+ # Authentication scheme.
217
+ attr_reader :scheme
218
+
219
+ # Creates new BasicAuth filter.
220
+ def initialize
221
+ @cred = nil
222
+ @auth = {}
223
+ @challengeable = {}
224
+ @scheme = "Basic"
225
+ end
226
+
227
+ # Resets challenge state. Do not send '*Authorization' header until the
228
+ # server sends '*Authentication' again.
229
+ def reset_challenge
230
+ @challengeable.clear
231
+ end
232
+
233
+ # Set authentication credential.
234
+ # uri == nil for generic purpose (allow to use user/password for any URL).
235
+ def set(uri, user, passwd)
236
+ if uri.nil?
237
+ @cred = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
238
+ else
239
+ uri = Util.uri_dirname(uri)
240
+ @auth[uri] = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
241
+ end
242
+ end
243
+
244
+ # Response handler: returns credential.
245
+ # It sends cred only when a given uri is;
246
+ # * child page of challengeable(got *Authenticate before) uri and,
247
+ # * child page of defined credential
248
+ def get(req)
249
+ target_uri = req.header.request_uri
250
+ return nil unless @challengeable.find { |uri, ok|
251
+ Util.uri_part_of(target_uri, uri) and ok
252
+ }
253
+ return @cred if @cred
254
+ Util.hash_find_value(@auth) { |uri, cred|
255
+ Util.uri_part_of(target_uri, uri)
256
+ }
257
+ end
258
+
259
+ # Challenge handler: remember URL for response.
260
+ def challenge(uri, param_str = nil)
261
+ @challengeable[urify(uri)] = true
262
+ true
263
+ end
264
+ end
265
+
266
+
267
+ # Authentication filter for handling DigestAuth negotiation.
268
+ # Used in WWWAuth.
269
+ class DigestAuth
270
+ # Authentication scheme.
271
+ attr_reader :scheme
272
+
273
+ # Creates new DigestAuth filter.
274
+ def initialize
275
+ @auth = {}
276
+ @challenge = {}
277
+ @nonce_count = 0
278
+ @scheme = "Digest"
279
+ end
280
+
281
+ # Resets challenge state. Do not send '*Authorization' header until the
282
+ # server sends '*Authentication' again.
283
+ def reset_challenge
284
+ @challenge.clear
285
+ end
286
+
287
+ # Set authentication credential.
288
+ # uri == nil is ignored.
289
+ def set(uri, user, passwd)
290
+ if uri
291
+ uri = Util.uri_dirname(uri)
292
+ @auth[uri] = [user, passwd]
293
+ end
294
+ end
295
+
296
+ # Response handler: returns credential.
297
+ # It sends cred only when a given uri is;
298
+ # * child page of challengeable(got *Authenticate before) uri and,
299
+ # * child page of defined credential
300
+ def get(req)
301
+ target_uri = req.header.request_uri
302
+ param = Util.hash_find_value(@challenge) { |uri, v|
303
+ Util.uri_part_of(target_uri, uri)
304
+ }
305
+ return nil unless param
306
+ user, passwd = Util.hash_find_value(@auth) { |uri, auth_data|
307
+ Util.uri_part_of(target_uri, uri)
308
+ }
309
+ return nil unless user
310
+ uri = req.header.request_uri
311
+ calc_cred(req.header.request_method, uri, user, passwd, param)
312
+ end
313
+
314
+ # Challenge handler: remember URL and challenge token for response.
315
+ def challenge(uri, param_str)
316
+ @challenge[uri] = parse_challenge_param(param_str)
317
+ true
318
+ end
319
+
320
+ private
321
+
322
+ # this method is implemented by sromano and posted to
323
+ # http://tools.assembla.com/breakout/wiki/DigestForSoap
324
+ # Thanks!
325
+ # supported algorithm: MD5 only for now
326
+ def calc_cred(method, uri, user, passwd, param)
327
+ url = uri.path
328
+ url = "#{url}?#{uri.query}" if uri.query
329
+ a_1 = "#{user}:#{param['realm']}:#{passwd}"
330
+ a_2 = "#{method}:#{url}"
331
+ nonce = param['nonce']
332
+ cnonce = generate_cnonce()
333
+ @nonce_count += 1
334
+ message_digest = []
335
+ message_digest << Digest::MD5.hexdigest(a_1)
336
+ message_digest << nonce
337
+ message_digest << ('%08x' % @nonce_count)
338
+ message_digest << cnonce
339
+ message_digest << param['qop']
340
+ message_digest << Digest::MD5.hexdigest(a_2)
341
+ header = []
342
+ header << "username=\"#{user}\""
343
+ header << "realm=\"#{param['realm']}\""
344
+ header << "nonce=\"#{nonce}\""
345
+ header << "uri=\"#{uri.path}\""
346
+ header << "cnonce=\"#{cnonce}\""
347
+ header << "nc=#{'%08x' % @nonce_count}"
348
+ header << "qop=\"#{param['qop']}\""
349
+ header << "response=\"#{Digest::MD5.hexdigest(message_digest.join(":"))}\""
350
+ header << "algorithm=\"MD5\""
351
+ header << "opaque=\"#{param['opaque']}\"" if param.key?('opaque')
352
+ header.join(", ")
353
+ end
354
+
355
+ # cf. WEBrick::HTTPAuth::DigestAuth#generate_next_nonce(aTime)
356
+ def generate_cnonce
357
+ now = "%012d" % Time.now.to_i
358
+ pk = Digest::MD5.hexdigest([now, self.__id__, Process.pid, rand(65535)].join)[0, 32]
359
+ [now + ':' + pk].pack('m*').chop
360
+ end
361
+
362
+ def parse_challenge_param(param_str)
363
+ param = {}
364
+ param_str.scan(/\s*([^\,]+(?:\\.[^\,]*)*)/).each do |str|
365
+ key, value = str[0].scan(/\A([^=]+)=(.*)\z/)[0]
366
+ if /\A"(.*)"\z/ =~ value
367
+ value = $1.gsub(/\\(.)/, '\1')
368
+ end
369
+ param[key] = value
370
+ end
371
+ param
372
+ end
373
+ end
374
+
375
+
376
+ # Authentication filter for handling Negotiate/NTLM negotiation.
377
+ # Used in WWWAuth and ProxyAuth.
378
+ #
379
+ # NegotiateAuth depends on 'ruby/ntlm' module.
380
+ class NegotiateAuth
381
+ # Authentication scheme.
382
+ attr_reader :scheme
383
+ # NTLM opt for ruby/ntlm. {:ntlmv2 => true} by default.
384
+ attr_reader :ntlm_opt
385
+
386
+ # Creates new NegotiateAuth filter.
387
+ def initialize(scheme = "Negotiate")
388
+ @auth = {}
389
+ @auth_default = nil
390
+ @challenge = {}
391
+ @scheme = scheme
392
+ @ntlm_opt = {
393
+ :ntlmv2 => true
394
+ }
395
+ end
396
+
397
+ # Resets challenge state. Do not send '*Authorization' header until the
398
+ # server sends '*Authentication' again.
399
+ def reset_challenge
400
+ @challenge.clear
401
+ end
402
+
403
+ # Set authentication credential.
404
+ # uri == nil for generic purpose (allow to use user/password for any URL).
405
+ def set(uri, user, passwd)
406
+ if uri
407
+ uri = Util.uri_dirname(uri)
408
+ @auth[uri] = [user, passwd]
409
+ else
410
+ @auth_default = [user, passwd]
411
+ end
412
+ end
413
+
414
+ # Response handler: returns credential.
415
+ # See ruby/ntlm for negotiation state transition.
416
+ def get(req)
417
+ return nil unless NTLMEnabled
418
+ target_uri = req.header.request_uri
419
+ domain_uri, param = @challenge.find { |uri, v|
420
+ Util.uri_part_of(target_uri, uri)
421
+ }
422
+ return nil unless param
423
+ user, passwd = Util.hash_find_value(@auth) { |uri, auth_data|
424
+ Util.uri_part_of(target_uri, uri)
425
+ }
426
+ unless user
427
+ user, passwd = @auth_default
428
+ end
429
+ return nil unless user
430
+ state = param[:state]
431
+ authphrase = param[:authphrase]
432
+ case state
433
+ when :init
434
+ t1 = Net::NTLM::Message::Type1.new
435
+ return t1.encode64
436
+ when :response
437
+ t2 = Net::NTLM::Message.decode64(authphrase)
438
+ t3 = t2.response({:user => user, :password => passwd}, @ntlm_opt.dup)
439
+ @challenge.delete(domain_uri)
440
+ return t3.encode64
441
+ end
442
+ nil
443
+ end
444
+
445
+ # Challenge handler: remember URL and challenge token for response.
446
+ def challenge(uri, param_str)
447
+ return false unless NTLMEnabled
448
+ if param_str.nil? or @challenge[uri].nil?
449
+ c = @challenge[uri] = {}
450
+ c[:state] = :init
451
+ c[:authphrase] = ""
452
+ else
453
+ c = @challenge[uri]
454
+ c[:state] = :response
455
+ c[:authphrase] = param_str
456
+ end
457
+ true
458
+ end
459
+ end
460
+
461
+
462
+ # Authentication filter for handling Negotiate/NTLM negotiation.
463
+ # Used in ProxyAuth.
464
+ #
465
+ # SSPINegotiateAuth depends on 'win32/sspi' module.
466
+ class SSPINegotiateAuth
467
+ # Authentication scheme.
468
+ attr_reader :scheme
469
+
470
+ # Creates new SSPINegotiateAuth filter.
471
+ def initialize
472
+ @challenge = {}
473
+ @scheme = "Negotiate"
474
+ end
475
+
476
+ # Resets challenge state. Do not send '*Authorization' header until the
477
+ # server sends '*Authentication' again.
478
+ def reset_challenge
479
+ @challenge.clear
480
+ end
481
+
482
+ # Set authentication credential.
483
+ # NOT SUPPORTED: username and necessary data is retrieved by win32/sspi.
484
+ # See win32/sspi for more details.
485
+ def set(uri, user, passwd)
486
+ # not supported
487
+ end
488
+
489
+ # Response handler: returns credential.
490
+ # See win32/sspi for negotiation state transition.
491
+ def get(req)
492
+ return nil unless SSPIEnabled
493
+ target_uri = req.header.request_uri
494
+ domain_uri, param = @challenge.find { |uri, v|
495
+ Util.uri_part_of(target_uri, uri)
496
+ }
497
+ return nil unless param
498
+ state = param[:state]
499
+ authenticator = param[:authenticator]
500
+ authphrase = param[:authphrase]
501
+ case state
502
+ when :init
503
+ authenticator = param[:authenticator] = Win32::SSPI::NegotiateAuth.new
504
+ return authenticator.get_initial_token(@scheme)
505
+ when :response
506
+ @challenge.delete(domain_uri)
507
+ return authenticator.complete_authentication(authphrase)
508
+ end
509
+ nil
510
+ end
511
+
512
+ # Challenge handler: remember URL and challenge token for response.
513
+ def challenge(uri, param_str)
514
+ return false unless SSPIEnabled
515
+ if param_str.nil? or @challenge[uri].nil?
516
+ c = @challenge[uri] = {}
517
+ c[:state] = :init
518
+ c[:authenticator] = nil
519
+ c[:authphrase] = ""
520
+ else
521
+ c = @challenge[uri]
522
+ c[:state] = :response
523
+ c[:authphrase] = param_str
524
+ end
525
+ true
526
+ end
527
+ end
528
+
529
+ # Authentication filter for handling OAuth negotiation.
530
+ # Used in WWWAuth.
531
+ #
532
+ # CAUTION: This impl only support '#7 Accessing Protected Resources' in OAuth
533
+ # Core 1.0 spec for now. You need to obtain Access token and Access secret by
534
+ # yourself.
535
+ #
536
+ # CAUTION: This impl does NOT support OAuth Request Body Hash spec for now.
537
+ # http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
538
+ #
539
+ class OAuth
540
+ include HTTPClient::Util
541
+
542
+ # Authentication scheme.
543
+ attr_reader :scheme
544
+
545
+ class Config
546
+ include HTTPClient::Util
547
+
548
+ attr_accessor :http_method
549
+ attr_accessor :realm
550
+ attr_accessor :consumer_key
551
+ attr_accessor :consumer_secret
552
+ attr_accessor :token
553
+ attr_accessor :secret
554
+ attr_accessor :signature_method
555
+ attr_accessor :version
556
+ attr_accessor :callback
557
+ attr_accessor :verifier
558
+ attr_reader :signature_handler
559
+
560
+ attr_accessor :debug_timestamp
561
+ attr_accessor :debug_nonce
562
+
563
+ def initialize(*args)
564
+ @http_method,
565
+ @realm,
566
+ @consumer_key,
567
+ @consumer_secret,
568
+ @token,
569
+ @secret,
570
+ @signature_method,
571
+ @version,
572
+ @callback,
573
+ @verifier =
574
+ keyword_argument(args,
575
+ :http_method,
576
+ :realm,
577
+ :consumer_key,
578
+ :consumer_secret,
579
+ :token,
580
+ :secret,
581
+ :signature_method,
582
+ :version,
583
+ :callback,
584
+ :verifier
585
+ )
586
+ @http_method ||= :post
587
+ @signature_handler = {}
588
+ end
589
+ end
590
+
591
+ def self.escape(str) # :nodoc:
592
+ if str.respond_to?(:force_encoding)
593
+ s = str.dup.force_encoding('BINARY').gsub(/([^a-zA-Z0-9_.~-]+)/) {
594
+ '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
595
+ }
596
+ else
597
+ str.gsub(/([^a-zA-Z0-9_.~-]+)/n) {
598
+ '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
599
+ }
600
+ end
601
+ end
602
+
603
+ def escape(str)
604
+ self.class.escape(str)
605
+ end
606
+
607
+ # Creates new DigestAuth filter.
608
+ def initialize
609
+ @config = nil # common config
610
+ @auth = {} # configs for each site
611
+ @challengeable = {}
612
+ @nonce_count = 0
613
+ @signature_handler = {
614
+ 'HMAC-SHA1' => method(:sign_hmac_sha1)
615
+ }
616
+ @scheme = "OAuth"
617
+ end
618
+
619
+ # Resets challenge state. Do not send '*Authorization' header until the
620
+ # server sends '*Authentication' again.
621
+ def reset_challenge
622
+ @challengeable.clear
623
+ end
624
+
625
+ # Set authentication credential.
626
+ # You cannot set OAuth config via WWWAuth#set_auth. Use OAuth#config=
627
+ def set(uri, user, passwd)
628
+ # not supported
629
+ end
630
+
631
+ # Set authentication credential.
632
+ def set_config(uri, config)
633
+ if uri.nil?
634
+ @config = config
635
+ else
636
+ uri = Util.uri_dirname(urify(uri))
637
+ @auth[uri] = config
638
+ end
639
+ end
640
+
641
+ # Get authentication credential.
642
+ def get_config(uri = nil)
643
+ if uri.nil?
644
+ @config
645
+ else
646
+ uri = urify(uri)
647
+ Util.hash_find_value(@auth) { |cand_uri, cred|
648
+ Util.uri_part_of(uri, cand_uri)
649
+ }
650
+ end
651
+ end
652
+
653
+ # Response handler: returns credential.
654
+ # It sends cred only when a given uri is;
655
+ # * child page of challengeable(got *Authenticate before) uri and,
656
+ # * child page of defined credential
657
+ def get(req)
658
+ target_uri = req.header.request_uri
659
+ return nil unless @challengeable[nil] or @challengeable.find { |uri, ok|
660
+ Util.uri_part_of(target_uri, uri) and ok
661
+ }
662
+ config = get_config(target_uri) || @config
663
+ return nil unless config
664
+ calc_cred(req, config)
665
+ end
666
+
667
+ # Challenge handler: remember URL for response.
668
+ def challenge(uri, param_str = nil)
669
+ if uri.nil?
670
+ @challengeable[nil] = true
671
+ else
672
+ @challengeable[urify(uri)] = true
673
+ end
674
+ true
675
+ end
676
+
677
+ private
678
+
679
+ def calc_cred(req, config)
680
+ header = {}
681
+ header['oauth_consumer_key'] = config.consumer_key
682
+ header['oauth_token'] = config.token
683
+ header['oauth_signature_method'] = config.signature_method
684
+ header['oauth_timestamp'] = config.debug_timestamp || Time.now.to_i.to_s
685
+ header['oauth_nonce'] = config.debug_nonce || generate_nonce()
686
+ header['oauth_version'] = config.version if config.version
687
+ header['oauth_callback'] = config.callback if config.callback
688
+ header['oauth_verifier'] = config.verifier if config.verifier
689
+ signature = sign(config, header, req)
690
+ header['oauth_signature'] = signature
691
+ # no need to do but we should sort for easier to test.
692
+ str = header.sort_by { |k, v| k }.map { |k, v| encode_header(k, v) }.join(', ')
693
+ if config.realm
694
+ str = %Q(realm="#{config.realm}", ) + str
695
+ end
696
+ str
697
+ end
698
+
699
+ def generate_nonce
700
+ @nonce_count += 1
701
+ now = "%012d" % Time.now.to_i
702
+ pk = Digest::MD5.hexdigest([@nonce_count.to_s, now, self.__id__, Process.pid, rand(65535)].join)[0, 32]
703
+ [now + ':' + pk].pack('m*').chop
704
+ end
705
+
706
+ def encode_header(k, v)
707
+ %Q(#{escape(k.to_s)}="#{escape(v.to_s)}")
708
+ end
709
+
710
+ def encode_param(params)
711
+ params.map { |k, v|
712
+ [v].flatten.map { |vv|
713
+ %Q(#{escape(k.to_s)}=#{escape(vv.to_s)})
714
+ }
715
+ }.flatten
716
+ end
717
+
718
+ def sign(config, header, req)
719
+ base_string = create_base_string(config, header, req)
720
+ if handler = config.signature_handler[config.signature_method] || @signature_handler[config.signature_method.to_s]
721
+ handler.call(config, base_string)
722
+ else
723
+ raise ConfigurationError.new("Unknown OAuth signature method: #{config.signature_method}")
724
+ end
725
+ end
726
+
727
+ def create_base_string(config, header, req)
728
+ params = encode_param(header)
729
+ query = req.header.request_query
730
+ if query and HTTP::Message.multiparam_query?(query)
731
+ params += encode_param(query)
732
+ end
733
+ # captures HTTP Message body only for 'application/x-www-form-urlencoded'
734
+ if req.header.contenttype == 'application/x-www-form-urlencoded' and req.body.size
735
+ params += encode_param(HTTP::Message.parse(req.body.content))
736
+ end
737
+ uri = req.header.request_uri
738
+ if uri.query
739
+ params += encode_param(HTTP::Message.parse(uri.query))
740
+ end
741
+ if uri.port == uri.default_port
742
+ request_url = "#{uri.scheme.downcase}://#{uri.host}#{uri.path}"
743
+ else
744
+ request_url = "#{uri.scheme.downcase}://#{uri.host}:#{uri.port}#{uri.path}"
745
+ end
746
+ [req.header.request_method.upcase, request_url, params.sort.join('&')].map { |e|
747
+ escape(e)
748
+ }.join('&')
749
+ end
750
+
751
+ def sign_hmac_sha1(config, base_string)
752
+ unless SSLEnabled
753
+ raise ConfigurationError.new("openssl required for OAuth implementation")
754
+ end
755
+ key = [escape(config.consumer_secret.to_s), escape(config.secret.to_s)].join('&')
756
+ digester = OpenSSL::Digest::SHA1.new
757
+ [OpenSSL::HMAC.digest(digester, key, base_string)].pack('m*').chomp
758
+ end
759
+ end
760
+
761
+
762
+ end