signet 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1012 @@
1
+ # Copyright (C) 2010 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'addressable/uri'
16
+ require 'signet/oauth_1'
17
+ require 'signet/oauth_1/credential'
18
+ require 'signet/errors'
19
+
20
+ module Signet #:nodoc:
21
+ module OAuth1
22
+ class Client
23
+ ##
24
+ # Creates an OAuth 1.0 client.
25
+ #
26
+ # @param [Hash] options
27
+ # The configuration parameters for the client.
28
+ # - <code>:temporary_credential_uri</code> —
29
+ # The OAuth temporary credentials URI.
30
+ # - <code>:authorization_uri</code> — The OAuth authorization URI.
31
+ # - <code>:token_credential_uri</code> —
32
+ # The OAuth token credentials URI.
33
+ # - <code>:client_credential_key</code> —
34
+ # The OAuth client credential key.
35
+ # - <code>:client_credential_secret</code> —
36
+ # The OAuth client credential secret.
37
+ # - <code>:callback</code> — The OAuth callback. Defaults to 'oob'.
38
+ #
39
+ # @example
40
+ # client = Signet::OAuth1::Client.new(
41
+ # :temporary_credential_uri =>
42
+ # 'https://www.google.com/accounts/OAuthGetRequestToken',
43
+ # :authorization_uri =>
44
+ # 'https://www.google.com/accounts/OAuthAuthorizeToken',
45
+ # :token_credential_uri =>
46
+ # 'https://www.google.com/accounts/OAuthGetAccessToken',
47
+ # :client_credential_key => 'anonymous',
48
+ # :client_credential_secret => 'anonymous'
49
+ # )
50
+ def initialize(options={})
51
+ self.temporary_credential_uri = options[:temporary_credential_uri]
52
+ self.authorization_uri = options[:authorization_uri]
53
+ self.token_credential_uri = options[:token_credential_uri]
54
+ # Technically... this would allow you to pass in a :client key...
55
+ # But that would be weird. Don't do that.
56
+ self.client_credential_key =
57
+ Signet::OAuth1.extract_credential_key_option(:client, options)
58
+ self.client_credential_secret =
59
+ Signet::OAuth1.extract_credential_secret_option(:client, options)
60
+ self.temporary_credential_key =
61
+ Signet::OAuth1.extract_credential_key_option(:temporary, options)
62
+ self.temporary_credential_secret =
63
+ Signet::OAuth1.extract_credential_secret_option(:temporary, options)
64
+ self.token_credential_key =
65
+ Signet::OAuth1.extract_credential_key_option(:token, options)
66
+ self.token_credential_secret =
67
+ Signet::OAuth1.extract_credential_secret_option(:token, options)
68
+ self.callback = options[:callback]
69
+ end
70
+
71
+ ##
72
+ # Returns the temporary credentials URI for this client.
73
+ #
74
+ # @return [Addressable::URI] The temporary credentials URI.
75
+ def temporary_credential_uri
76
+ return @temporary_credential_uri
77
+ end
78
+ alias_method :request_token_uri, :temporary_credential_uri
79
+
80
+ ##
81
+ # Sets the temporary credentials URI for this client.
82
+ #
83
+ # @param [Addressable::URI, String, #to_str]
84
+ # new_temporary_credential_uri
85
+ # The temporary credentials URI.
86
+ def temporary_credential_uri=(new_temporary_credential_uri)
87
+ if new_temporary_credential_uri != nil
88
+ new_temporary_credential_uri =
89
+ Addressable::URI.parse(new_temporary_credential_uri)
90
+ @temporary_credential_uri = new_temporary_credential_uri
91
+ else
92
+ @temporary_credential_uri = nil
93
+ end
94
+ end
95
+ alias_method :request_token_uri=, :temporary_credential_uri=
96
+
97
+ ##
98
+ # Returns the authorization URI that the user should be redirected to.
99
+ #
100
+ # @return [Addressable::URI] The authorization URI.
101
+ #
102
+ # @see Signet::OAuth1.generate_authorization_uri
103
+ def authorization_uri(options={})
104
+ options = options.merge(
105
+ :temporary_credential_key => self.temporary_credential_key,
106
+ :callback => self.callback
107
+ )
108
+ return nil if @authorization_uri == nil
109
+ return Addressable::URI.parse(
110
+ ::Signet::OAuth1.generate_authorization_uri(
111
+ @authorization_uri, options
112
+ )
113
+ )
114
+ end
115
+
116
+ ##
117
+ # Sets the authorization URI for this client.
118
+ #
119
+ # @param [Addressable::URI, String, #to_str] new_authorization_uri
120
+ # The authorization URI.
121
+ def authorization_uri=(new_authorization_uri)
122
+ if new_authorization_uri != nil
123
+ new_authorization_uri =
124
+ Addressable::URI.parse(new_authorization_uri)
125
+ @authorization_uri = new_authorization_uri
126
+ else
127
+ @authorization_uri = nil
128
+ end
129
+ end
130
+
131
+ ##
132
+ # Returns the token credential URI for this client.
133
+ #
134
+ # @return [Addressable::URI] The token credential URI.
135
+ def token_credential_uri
136
+ return @token_credential_uri
137
+ end
138
+ alias_method :access_token_uri, :token_credential_uri
139
+
140
+ ##
141
+ # Sets the token credential URI for this client.
142
+ #
143
+ # @param [Addressable::URI, String, #to_str] new_token_credential_uri
144
+ # The token credential URI.
145
+ def token_credential_uri=(new_token_credential_uri)
146
+ if new_token_credential_uri != nil
147
+ new_token_credential_uri =
148
+ Addressable::URI.parse(new_token_credential_uri)
149
+ @token_credential_uri = new_token_credential_uri
150
+ else
151
+ @token_credential_uri = nil
152
+ end
153
+ end
154
+ alias_method :access_token_uri=, :token_credential_uri=
155
+
156
+ # Lots of duplicated code here, but for the sake of auto-generating
157
+ # documentation, we're going to let it slide. Oh well.
158
+
159
+ ##
160
+ # Returns the client credential for this client.
161
+ #
162
+ # @return [Signet::OAuth1::Credential] The client credentials.
163
+ def client_credential
164
+ if self.client_credential_key && self.client_credential_secret
165
+ return ::Signet::OAuth1::Credential.new(
166
+ self.client_credential_key,
167
+ self.client_credential_secret
168
+ )
169
+ elsif !self.client_credential_key && !self.client_credential_secret
170
+ return nil
171
+ else
172
+ raise ArgumentError,
173
+ "The client credential key and secret must be set."
174
+ end
175
+ end
176
+ alias_method :consumer_token, :client_credential
177
+
178
+ ##
179
+ # Sets the client credential for this client.
180
+ #
181
+ # @param [Signet::OAuth1::Credential] new_client_credential
182
+ # The client credentials.
183
+ def client_credential=(new_client_credential)
184
+ if new_client_credential != nil
185
+ if !new_client_credential.kind_of?(::Signet::OAuth1::Credential)
186
+ raise TypeError,
187
+ "Expected Signet::OAuth1::Credential, " +
188
+ "got #{new_client_credential.class}."
189
+ end
190
+ @client_credential_key = new_client_credential.key
191
+ @client_credential_secret = new_client_credential.secret
192
+ else
193
+ @client_credential_key = nil
194
+ @client_credential_secret = nil
195
+ end
196
+ end
197
+ alias_method :consumer_token=, :client_credential=
198
+
199
+ ##
200
+ # Returns the client credential key for this client.
201
+ #
202
+ # @return [String] The client credential key.
203
+ def client_credential_key
204
+ return @client_credential_key
205
+ end
206
+ alias_method :consumer_key, :client_credential_key
207
+
208
+ ##
209
+ # Sets the client credential key for this client.
210
+ #
211
+ # @param [String, #to_str] new_client_credential_key
212
+ # The client credential key.
213
+ def client_credential_key=(new_client_credential_key)
214
+ if new_client_credential_key != nil
215
+ if !new_client_credential_key.respond_to?(:to_str)
216
+ raise TypeError,
217
+ "Can't convert #{new_client_credential_key.class} into String."
218
+ end
219
+ new_client_credential_key = new_client_credential_key.to_str
220
+ @client_credential_key = new_client_credential_key
221
+ else
222
+ @client_credential_key = nil
223
+ end
224
+ end
225
+ alias_method :consumer_key=, :client_credential_key=
226
+
227
+ ##
228
+ # Returns the client credential secret for this client.
229
+ #
230
+ # @return [String] The client credential secret.
231
+ def client_credential_secret
232
+ return @client_credential_secret
233
+ end
234
+ alias_method :consumer_secret, :client_credential_secret
235
+
236
+ ##
237
+ # Sets the client credential secret for this client.
238
+ #
239
+ # @param [String, #to_str] new_client_credential_secret
240
+ # The client credential secret.
241
+ def client_credential_secret=(new_client_credential_secret)
242
+ if new_client_credential_secret != nil
243
+ if !new_client_credential_secret.respond_to?(:to_str)
244
+ raise TypeError,
245
+ "Can't convert #{new_client_credential_secret.class} " +
246
+ "into String."
247
+ end
248
+ new_client_credential_secret = new_client_credential_secret.to_str
249
+ @client_credential_secret = new_client_credential_secret
250
+ else
251
+ @client_credential_secret = nil
252
+ end
253
+ end
254
+ alias_method :consumer_secret=, :client_credential_secret=
255
+
256
+ ##
257
+ # Returns the temporary credential for this client.
258
+ #
259
+ # @return [Signet::OAuth1::Credential] The temporary credentials.
260
+ def temporary_credential
261
+ if self.temporary_credential_key && self.temporary_credential_secret
262
+ return ::Signet::OAuth1::Credential.new(
263
+ self.temporary_credential_key,
264
+ self.temporary_credential_secret
265
+ )
266
+ elsif !self.temporary_credential_key &&
267
+ !self.temporary_credential_secret
268
+ return nil
269
+ else
270
+ raise ArgumentError,
271
+ "The temporary credential key and secret must be set."
272
+ end
273
+ end
274
+ alias_method :request_token, :temporary_credential
275
+
276
+ ##
277
+ # Sets the temporary credential for this client.
278
+ #
279
+ # @param [Signet::OAuth1::Credential] new_temporary_credential
280
+ # The temporary credentials.
281
+ def temporary_credential=(new_temporary_credential)
282
+ if new_temporary_credential != nil
283
+ if !new_temporary_credential.kind_of?(::Signet::OAuth1::Credential)
284
+ raise TypeError,
285
+ "Expected Signet::OAuth1::Credential, " +
286
+ "got #{new_temporary_credential.class}."
287
+ end
288
+ @temporary_credential_key = new_temporary_credential.key
289
+ @temporary_credential_secret = new_temporary_credential.secret
290
+ else
291
+ @temporary_credential_key = nil
292
+ @temporary_credential_secret = nil
293
+ end
294
+ end
295
+ alias_method :request_token=, :temporary_credential=
296
+
297
+ ##
298
+ # Returns the temporary credential key for this client.
299
+ #
300
+ # @return [String] The temporary credential key.
301
+ def temporary_credential_key
302
+ return @temporary_credential_key
303
+ end
304
+ alias_method :request_token_key, :temporary_credential_key
305
+
306
+ ##
307
+ # Sets the temporary credential key for this client.
308
+ #
309
+ # @param [String, #to_str] new_temporary_credential_key
310
+ # The temporary credential key.
311
+ def temporary_credential_key=(new_temporary_credential_key)
312
+ if new_temporary_credential_key != nil
313
+ if !new_temporary_credential_key.respond_to?(:to_str)
314
+ raise TypeError,
315
+ "Can't convert #{new_temporary_credential_key.class} " +
316
+ "into String."
317
+ end
318
+ new_temporary_credential_key = new_temporary_credential_key.to_str
319
+ @temporary_credential_key = new_temporary_credential_key
320
+ else
321
+ @temporary_credential_key = nil
322
+ end
323
+ end
324
+ alias_method :request_token_key=, :temporary_credential_key=
325
+
326
+ ##
327
+ # Returns the temporary credential secret for this client.
328
+ #
329
+ # @return [String] The temporary credential secret.
330
+ def temporary_credential_secret
331
+ return @temporary_credential_secret
332
+ end
333
+ alias_method :request_token_secret, :temporary_credential_secret
334
+
335
+ ##
336
+ # Sets the temporary credential secret for this client.
337
+ #
338
+ # @param [String, #to_str] new_temporary_credential_secret
339
+ # The temporary credential secret.
340
+ def temporary_credential_secret=(new_temporary_credential_secret)
341
+ if new_temporary_credential_secret != nil
342
+ if !new_temporary_credential_secret.respond_to?(:to_str)
343
+ raise TypeError,
344
+ "Can't convert #{new_temporary_credential_secret.class} " +
345
+ "into String."
346
+ end
347
+ new_temporary_credential_secret =
348
+ new_temporary_credential_secret.to_str
349
+ @temporary_credential_secret = new_temporary_credential_secret
350
+ else
351
+ @temporary_credential_secret = nil
352
+ end
353
+ end
354
+ alias_method :request_token_secret=, :temporary_credential_secret=
355
+
356
+ ##
357
+ # Returns the token credential for this client.
358
+ #
359
+ # @return [Signet::OAuth1::Credential] The token credentials.
360
+ def token_credential
361
+ if self.token_credential_key && self.token_credential_secret
362
+ return ::Signet::OAuth1::Credential.new(
363
+ self.token_credential_key,
364
+ self.token_credential_secret
365
+ )
366
+ elsif !self.token_credential_key &&
367
+ !self.token_credential_secret
368
+ return nil
369
+ else
370
+ raise ArgumentError,
371
+ "The token credential key and secret must be set."
372
+ end
373
+ end
374
+ alias_method :access_token, :token_credential
375
+
376
+ ##
377
+ # Sets the token credential for this client.
378
+ #
379
+ # @param [Signet::OAuth1::Credential] new_token_credential
380
+ # The token credentials.
381
+ def token_credential=(new_token_credential)
382
+ if new_token_credential != nil
383
+ if !new_token_credential.kind_of?(::Signet::OAuth1::Credential)
384
+ raise TypeError,
385
+ "Expected Signet::OAuth1::Credential, " +
386
+ "got #{new_token_credential.class}."
387
+ end
388
+ @token_credential_key = new_token_credential.key
389
+ @token_credential_secret = new_token_credential.secret
390
+ else
391
+ @token_credential_key = nil
392
+ @token_credential_secret = nil
393
+ end
394
+ end
395
+ alias_method :access_token=, :token_credential=
396
+
397
+ ##
398
+ # Returns the token credential key for this client.
399
+ #
400
+ # @return [String] The token credential key.
401
+ def token_credential_key
402
+ return @token_credential_key
403
+ end
404
+ alias_method :access_token_key, :token_credential_key
405
+
406
+ ##
407
+ # Sets the token credential key for this client.
408
+ #
409
+ # @param [String, #to_str] new_token_credential_key
410
+ # The token credential key.
411
+ def token_credential_key=(new_token_credential_key)
412
+ if new_token_credential_key != nil
413
+ if !new_token_credential_key.respond_to?(:to_str)
414
+ raise TypeError,
415
+ "Can't convert #{new_token_credential_key.class} " +
416
+ "into String."
417
+ end
418
+ new_token_credential_key = new_token_credential_key.to_str
419
+ @token_credential_key = new_token_credential_key
420
+ else
421
+ @token_credential_key = nil
422
+ end
423
+ end
424
+ alias_method :access_token_key=, :token_credential_key=
425
+
426
+ ##
427
+ # Returns the token credential secret for this client.
428
+ #
429
+ # @return [String] The token credential secret.
430
+ def token_credential_secret
431
+ return @token_credential_secret
432
+ end
433
+ alias_method :access_token_secret, :token_credential_secret
434
+
435
+ ##
436
+ # Sets the token credential secret for this client.
437
+ #
438
+ # @param [String, #to_str] new_token_credential_secret
439
+ # The token credential secret.
440
+ def token_credential_secret=(new_token_credential_secret)
441
+ if new_token_credential_secret != nil
442
+ if !new_token_credential_secret.respond_to?(:to_str)
443
+ raise TypeError,
444
+ "Can't convert #{new_token_credential_secret.class} " +
445
+ "into String."
446
+ end
447
+ new_token_credential_secret =
448
+ new_token_credential_secret.to_str
449
+ @token_credential_secret = new_token_credential_secret
450
+ else
451
+ @token_credential_secret = nil
452
+ end
453
+ end
454
+ alias_method :access_token_secret=, :token_credential_secret=
455
+
456
+ ##
457
+ # Returns the callback for this client.
458
+ #
459
+ # @return [String] The OAuth callback.
460
+ def callback
461
+ return @callback || ::Signet::OAuth1::OUT_OF_BAND
462
+ end
463
+
464
+ ##
465
+ # Sets the callback for this client.
466
+ #
467
+ # @param [String, #to_str] new_callback
468
+ # The OAuth callback.
469
+ def callback=(new_callback)
470
+ if new_callback != nil
471
+ if !new_callback.respond_to?(:to_str)
472
+ raise TypeError,
473
+ "Can't convert #{new_callback.class} into String."
474
+ end
475
+ new_callback = new_callback.to_str
476
+ @callback = new_callback
477
+ else
478
+ @callback = nil
479
+ end
480
+ end
481
+
482
+ ##
483
+ # Generates a request for temporary credentials.
484
+ #
485
+ # @param [Hash] options
486
+ # The configuration parameters for the request.
487
+ # - <code>:signature_method</code> —
488
+ # The signature method. Defaults to <code>'HMAC-SHA1'</code>.
489
+ # - <code>:additional_parameters</code> —
490
+ # Non-standard additional parameters.
491
+ # - <code>:realm</code> —
492
+ # The Authorization realm. See RFC 2617.
493
+ #
494
+ # @return [Array] The request object.
495
+ def generate_temporary_credential_request(options={})
496
+ verifications = {
497
+ :temporary_credential_uri => 'Temporary credentials URI',
498
+ :client_credential_key => 'Client credential key',
499
+ :client_credential_secret => 'Client credential secret'
500
+ }
501
+ # Make sure all required state is set
502
+ verifications.each do |(key, value)|
503
+ unless self.send(key)
504
+ raise ArgumentError, "#{key} was not set."
505
+ end
506
+ end
507
+ options = {
508
+ :signature_method => 'HMAC-SHA1',
509
+ :additional_parameters => [],
510
+ :realm => nil
511
+ }.merge(options)
512
+ method = 'POST'
513
+ parameters = ::Signet::OAuth1.unsigned_temporary_credential_parameters(
514
+ :client_credential_key => self.client_credential_key,
515
+ :callback => self.callback,
516
+ :signature_method => options[:signature_method],
517
+ :additional_parameters => options[:additional_parameters]
518
+ )
519
+ signature = ::Signet::OAuth1.sign_parameters(
520
+ method,
521
+ self.temporary_credential_uri,
522
+ parameters,
523
+ self.client_credential_secret
524
+ )
525
+ parameters << ['oauth_signature', signature]
526
+ authorization_header = [
527
+ 'Authorization',
528
+ ::Signet::OAuth1.generate_authorization_header(
529
+ parameters, options[:realm]
530
+ )
531
+ ]
532
+ headers = [authorization_header]
533
+ if method == 'POST'
534
+ headers << ['Content-Type', 'application/x-www-form-urlencoded']
535
+ end
536
+ return [
537
+ method,
538
+ self.temporary_credential_uri.to_str,
539
+ headers,
540
+ ['']
541
+ ]
542
+ end
543
+ alias_method(
544
+ :generate_request_token_request,
545
+ :generate_temporary_credential_request
546
+ )
547
+
548
+ ##
549
+ # Transmits a request for a temporary credential. This method does not
550
+ # have side-effects within the client.
551
+ #
552
+ # @param [Hash] options
553
+ # The configuration parameters for the request.
554
+ # - <code>:signature_method</code> —
555
+ # The signature method. Defaults to <code>'HMAC-SHA1'</code>.
556
+ # - <code>:additional_parameters</code> —
557
+ # Non-standard additional parameters.
558
+ # - <code>:realm</code> —
559
+ # The Authorization realm. See RFC 2617.
560
+ # - <code>:adapter</code> —
561
+ # The HTTP adapter.
562
+ # Defaults to <code>HTTPAdapter::NetHTTPRequestAdapter</code>.
563
+ # - <code>:connection</code> —
564
+ # An open, manually managed HTTP connection.
565
+ # Must be of type <code>HTTPAdapter::Connection</code> and the
566
+ # internal connection representation must match the HTTP adapter
567
+ # being used.
568
+ #
569
+ # @return [Signet::OAuth1::Credential] The temporary credential.
570
+ #
571
+ # @example
572
+ # temporary_credential = client.fetch_temporary_credential(
573
+ # :additional_parameters => {
574
+ # :scope => 'https://mail.google.com/mail/feed/atom'
575
+ # }
576
+ # )
577
+ def fetch_temporary_credential(options={})
578
+ adapter = options[:adapter]
579
+ unless adapter
580
+ require 'httpadapter'
581
+ require 'httpadapter/adapters/net_http'
582
+ adapter = HTTPAdapter::NetHTTPRequestAdapter
583
+ end
584
+ connection = options[:connection]
585
+ request = self.generate_temporary_credential_request(options)
586
+ response = HTTPAdapter.transmit(request, adapter, connection)
587
+ status, headers, body = response
588
+ merged_body = StringIO.new
589
+ body.each do |chunk|
590
+ merged_body.write(chunk)
591
+ end
592
+ body = merged_body.string
593
+ if status.to_i == 200
594
+ return ::Signet::OAuth1.parse_form_encoded_credentials(body)
595
+ elsif [400, 401, 403].include?(status.to_i)
596
+ message = 'Authorization failed.'
597
+ if body.strip.length > 0
598
+ message += " Server message:\n#{body.strip}"
599
+ end
600
+ error = ::Signet::AuthorizationError.new(message, request, response)
601
+ raise error
602
+ else
603
+ message = "Unexpected status code: #{status}."
604
+ if body.strip.length > 0
605
+ message += " Server message:\n#{body.strip}"
606
+ end
607
+ error = ::Signet::AuthorizationError.new(message, request, response)
608
+ raise error
609
+ end
610
+ end
611
+ alias_method(
612
+ :fetch_request_token,
613
+ :fetch_temporary_credential
614
+ )
615
+
616
+ ##
617
+ # Transmits a request for a temporary credential. This method updates
618
+ # the client with the new temporary credential.
619
+ #
620
+ # @param [Hash] options
621
+ # The configuration parameters for the request.
622
+ # - <code>:signature_method</code> —
623
+ # The signature method. Defaults to <code>'HMAC-SHA1'</code>.
624
+ # - <code>:additional_parameters</code> —
625
+ # Non-standard additional parameters.
626
+ # - <code>:realm</code> —
627
+ # The Authorization realm. See RFC 2617.
628
+ # - <code>:adapter</code> —
629
+ # The HTTP adapter.
630
+ # Defaults to <code>HTTPAdapter::NetHTTPRequestAdapter</code>.
631
+ # - <code>:connection</code> —
632
+ # An open, manually managed HTTP connection.
633
+ # Must be of type <code>HTTPAdapter::Connection</code> and the
634
+ # internal connection representation must match the HTTP adapter
635
+ # being used.
636
+ #
637
+ # @return [Signet::OAuth1::Credential] The temporary credential.
638
+ #
639
+ # @example
640
+ # client.fetch_temporary_credential!(:additional_parameters => {
641
+ # :scope => 'https://mail.google.com/mail/feed/atom'
642
+ # })
643
+ def fetch_temporary_credential!(options={})
644
+ credential = self.fetch_temporary_credential(options)
645
+ self.temporary_credential = credential
646
+ end
647
+ alias_method(
648
+ :fetch_request_token!,
649
+ :fetch_temporary_credential!
650
+ )
651
+
652
+ ##
653
+ # Generates a request for token credentials.
654
+ #
655
+ # @param [Hash] options
656
+ # The configuration parameters for the request.
657
+ # - <code>:verifier</code> —
658
+ # The OAuth verifier provided by the server. Required.
659
+ # - <code>:signature_method</code> —
660
+ # The signature method. Defaults to <code>'HMAC-SHA1'</code>.
661
+ # - <code>:realm</code> —
662
+ # The Authorization realm. See RFC 2617.
663
+ #
664
+ # @return [Array] The request object.
665
+ def generate_token_credential_request(options={})
666
+ verifications = {
667
+ :token_credential_uri => 'Token credentials URI',
668
+ :client_credential_key => 'Client credential key',
669
+ :client_credential_secret => 'Client credential secret',
670
+ :temporary_credential_key => 'Temporary credential key',
671
+ :temporary_credential_secret => 'Temporary credential secret'
672
+ }
673
+ # Make sure all required state is set
674
+ verifications.each do |(key, value)|
675
+ unless self.send(key)
676
+ raise ArgumentError, "#{key} was not set."
677
+ end
678
+ end
679
+ options = {
680
+ :signature_method => 'HMAC-SHA1',
681
+ :realm => nil
682
+ }.merge(options)
683
+ method = 'POST'
684
+ parameters = ::Signet::OAuth1.unsigned_token_credential_parameters(
685
+ :client_credential_key => self.client_credential_key,
686
+ :temporary_credential_key => self.temporary_credential_key,
687
+ :signature_method => options[:signature_method],
688
+ :verifier => options[:verifier]
689
+ )
690
+ signature = ::Signet::OAuth1.sign_parameters(
691
+ method,
692
+ self.token_credential_uri,
693
+ parameters,
694
+ self.client_credential_secret,
695
+ self.temporary_credential_secret
696
+ )
697
+ parameters << ['oauth_signature', signature]
698
+ authorization_header = [
699
+ 'Authorization',
700
+ ::Signet::OAuth1.generate_authorization_header(
701
+ parameters, options[:realm]
702
+ )
703
+ ]
704
+ headers = [authorization_header]
705
+ if method == 'POST'
706
+ headers << ['Content-Type', 'application/x-www-form-urlencoded']
707
+ end
708
+ return [
709
+ method,
710
+ self.token_credential_uri.to_str,
711
+ headers,
712
+ ['']
713
+ ]
714
+ end
715
+ alias_method(
716
+ :generate_access_token_request,
717
+ :generate_token_credential_request
718
+ )
719
+
720
+ ##
721
+ # Transmits a request for a token credential. This method does not
722
+ # have side-effects within the client.
723
+ #
724
+ # @param [Hash] options
725
+ # The configuration parameters for the request.
726
+ # - <code>:verifier</code> —
727
+ # The OAuth verifier provided by the server. Required.
728
+ # - <code>:signature_method</code> —
729
+ # The signature method. Defaults to <code>'HMAC-SHA1'</code>.
730
+ # - <code>:realm</code> —
731
+ # The Authorization realm. See RFC 2617.
732
+ # - <code>:adapter</code> —
733
+ # The HTTP adapter.
734
+ # Defaults to <code>HTTPAdapter::NetHTTPRequestAdapter</code>.
735
+ # - <code>:connection</code> —
736
+ # An open, manually managed HTTP connection.
737
+ # Must be of type <code>HTTPAdapter::Connection</code> and the
738
+ # internal connection representation must match the HTTP adapter
739
+ # being used.
740
+ #
741
+ # @return [Signet::OAuth1::Credential] The token credential.
742
+ #
743
+ # @example
744
+ # token_credential = client.fetch_token_credential(
745
+ # :verifier => '12345'
746
+ # )
747
+ def fetch_token_credential(options={})
748
+ adapter = options[:adapter]
749
+ unless adapter
750
+ require 'httpadapter'
751
+ require 'httpadapter/adapters/net_http'
752
+ adapter = HTTPAdapter::NetHTTPRequestAdapter
753
+ end
754
+ connection = options[:connection]
755
+ request = self.generate_token_credential_request(options)
756
+ response = HTTPAdapter.transmit(request, adapter, connection)
757
+ status, headers, body = response
758
+ merged_body = StringIO.new
759
+ body.each do |chunk|
760
+ merged_body.write(chunk)
761
+ end
762
+ body = merged_body.string
763
+ if status.to_i == 200
764
+ return ::Signet::OAuth1.parse_form_encoded_credentials(body)
765
+ elsif [400, 401, 403].include?(status.to_i)
766
+ message = 'Authorization failed.'
767
+ if body.strip.length > 0
768
+ message += " Server message:\n#{body.strip}"
769
+ end
770
+ error = ::Signet::AuthorizationError.new(message, request, response)
771
+ raise error
772
+ else
773
+ message = "Unexpected status code: #{status}."
774
+ if body.strip.length > 0
775
+ message += " Server message:\n#{body.strip}"
776
+ end
777
+ error = ::Signet::AuthorizationError.new(message, request, response)
778
+ raise error
779
+ end
780
+ end
781
+ alias_method(
782
+ :fetch_access_token,
783
+ :fetch_token_credential
784
+ )
785
+
786
+ ##
787
+ # Transmits a request for a token credential. This method updates
788
+ # the client with the new token credential.
789
+ #
790
+ # @param [Hash] options
791
+ # The configuration parameters for the request.
792
+ # - <code>:signature_method</code> —
793
+ # The signature method. Defaults to <code>'HMAC-SHA1'</code>.
794
+ # - <code>:additional_parameters</code> —
795
+ # Non-standard additional parameters.
796
+ # - <code>:realm</code> —
797
+ # The Authorization realm. See RFC 2617.
798
+ # - <code>:adapter</code> —
799
+ # The HTTP adapter.
800
+ # Defaults to <code>HTTPAdapter::NetHTTPRequestAdapter</code>.
801
+ # - <code>:connection</code> —
802
+ # An open, manually managed HTTP connection.
803
+ # Must be of type <code>HTTPAdapter::Connection</code> and the
804
+ # internal connection representation must match the HTTP adapter
805
+ # being used.
806
+ #
807
+ # @return [Signet::OAuth1::Credential] The token credential.
808
+ #
809
+ # @example
810
+ # client.fetch_token_credential!(:verifier => '12345')
811
+ def fetch_token_credential!(options={})
812
+ credential = self.fetch_token_credential(options)
813
+ self.token_credential = credential
814
+ end
815
+ alias_method(
816
+ :fetch_access_token!,
817
+ :fetch_token_credential!
818
+ )
819
+
820
+ ##
821
+ # Generates an authenticated request for protected resources.
822
+ #
823
+ # @param [Hash] options
824
+ # The configuration parameters for the request.
825
+ # - <code>:request</code> —
826
+ # A pre-constructed request to sign.
827
+ # - <code>:method</code> —
828
+ # The HTTP method for the request. Defaults to 'GET'.
829
+ # - <code>:uri</code> —
830
+ # The URI for the request.
831
+ # - <code>:headers</code> —
832
+ # The HTTP headers for the request.
833
+ # - <code>:body</code> —
834
+ # The HTTP body for the request.
835
+ # - <code>:signature_method</code> —
836
+ # The signature method. Defaults to <code>'HMAC-SHA1'</code>.
837
+ # - <code>:realm</code> —
838
+ # The Authorization realm. See RFC 2617.
839
+ #
840
+ # @return [Array] The request object.
841
+ def generate_authenticated_request(options={})
842
+ verifications = {
843
+ :client_credential_key => 'Client credential key',
844
+ :client_credential_secret => 'Client credential secret',
845
+ :token_credential_key => 'Token credential key',
846
+ :token_credential_secret => 'Token credential secret'
847
+ }
848
+ # Make sure all required state is set
849
+ verifications.each do |(key, value)|
850
+ unless self.send(key)
851
+ raise ArgumentError, "#{key} was not set."
852
+ end
853
+ end
854
+ options = {
855
+ :signature_method => 'HMAC-SHA1',
856
+ :realm => nil
857
+ }.merge(options)
858
+ if options[:request]
859
+ if options[:request].kind_of?(Array)
860
+ request = options[:request]
861
+ elsif options[:adapter] || options[:request].respond_to?(:to_ary)
862
+ request =
863
+ HTTPAdapter.adapt_request(options[:request], options[:adapter])
864
+ end
865
+ method, uri, headers, body = request
866
+ else
867
+ method = options[:method] || 'GET'
868
+ uri = options[:uri]
869
+ headers = options[:headers] || []
870
+ body = options[:body] || ''
871
+ end
872
+ request_components = {
873
+ :method => method,
874
+ :uri => uri,
875
+ :headers => headers,
876
+ :body => body
877
+ }
878
+ # Verify that we have all pieces required to return an HTTP request
879
+ request_components.each do |(key, value)|
880
+ unless value
881
+ raise ArgumentError, "Missing :#{key} parameter."
882
+ end
883
+ end
884
+ if !body.kind_of?(String) && body.respond_to?(:each)
885
+ # Just in case we get a chunked body
886
+ merged_body = StringIO.new
887
+ body.each do |chunk|
888
+ merged_body.write(chunk)
889
+ end
890
+ body = merged_body.string
891
+ end
892
+ if !body.kind_of?(String)
893
+ raise TypeError, "Expected String, got #{body.class}."
894
+ end
895
+ method = method.to_s.upcase
896
+ parameters = ::Signet::OAuth1.unsigned_resource_parameters(
897
+ :client_credential_key => self.client_credential_key,
898
+ :token_credential_key => self.token_credential_key,
899
+ :signature_method => options[:signature_method]
900
+ )
901
+ media_type = nil
902
+ headers.each do |(header, value)|
903
+ if header.downcase == 'Content-Type'.downcase
904
+ media_type = value.gsub(/^([^;]+)(;.*?)?$/, '\1')
905
+ end
906
+ end
907
+ if method == 'POST' &&
908
+ media_type == 'application/x-www-form-urlencoded'
909
+ post_parameters = Addressable::URI.form_unencode(body)
910
+ else
911
+ post_parameters = []
912
+ end
913
+ parameters = parameters.concat(post_parameters)
914
+ # No need to attach URI query parameters, the .sign_parameters
915
+ # method takes care of that automatically.
916
+ signature = ::Signet::OAuth1.sign_parameters(
917
+ method,
918
+ uri,
919
+ parameters,
920
+ self.client_credential_secret,
921
+ self.token_credential_secret
922
+ )
923
+ parameters << ['oauth_signature', signature]
924
+ authorization_header = [
925
+ 'Authorization',
926
+ ::Signet::OAuth1.generate_authorization_header(
927
+ parameters, options[:realm]
928
+ )
929
+ ]
930
+ headers << authorization_header
931
+ return [method, uri.to_str, headers, [body]]
932
+ end
933
+
934
+ ##
935
+ # Transmits a request for a protected resource.
936
+ #
937
+ # @param [Hash] options
938
+ # The configuration parameters for the request.
939
+ # - <code>:request</code> —
940
+ # A pre-constructed request to sign.
941
+ # - <code>:method</code> —
942
+ # The HTTP method for the request. Defaults to 'GET'.
943
+ # - <code>:uri</code> —
944
+ # The URI for the request.
945
+ # - <code>:headers</code> —
946
+ # The HTTP headers for the request.
947
+ # - <code>:body</code> —
948
+ # The HTTP body for the request.
949
+ # - <code>:signature_method</code> —
950
+ # The signature method. Defaults to <code>'HMAC-SHA1'</code>.
951
+ # - <code>:realm</code> —
952
+ # The Authorization realm. See RFC 2617.
953
+ # - <code>:adapter</code> —
954
+ # The HTTP adapter.
955
+ # Defaults to <code>HTTPAdapter::NetHTTPRequestAdapter</code>.
956
+ # - <code>:connection</code> —
957
+ # An open, manually managed HTTP connection.
958
+ # Must be of type <code>HTTPAdapter::Connection</code> and the
959
+ # internal connection representation must match the HTTP adapter
960
+ # being used.
961
+ #
962
+ # @example
963
+ # # Using Net::HTTP
964
+ # response = client.fetch_protected_resource(
965
+ # :uri => 'http://www.example.com/protected/resource'
966
+ # )
967
+ # status, headers, body = response
968
+ #
969
+ # @example
970
+ # # Using Typhoeus
971
+ # response = client.fetch_protected_resource(
972
+ # :request => Typhoeus::Request.new(
973
+ # 'http://www.example.com/protected/resource'
974
+ # ),
975
+ # :adapter => HTTPAdapter::TyphoeusRequestAdapter,
976
+ # :connection => connection
977
+ # )
978
+ # status, headers, body = response
979
+ #
980
+ # @return [Array] The response object.
981
+ def fetch_protected_resource(options={})
982
+ adapter = options[:adapter]
983
+ unless adapter
984
+ require 'httpadapter'
985
+ require 'httpadapter/adapters/net_http'
986
+ adapter = HTTPAdapter::NetHTTPRequestAdapter
987
+ end
988
+ connection = options[:connection]
989
+ request = self.generate_authenticated_request(options)
990
+ response = HTTPAdapter.transmit(request, adapter, connection)
991
+ status, headers, body = response
992
+ merged_body = StringIO.new
993
+ body.each do |chunk|
994
+ merged_body.write(chunk)
995
+ end
996
+ body = merged_body.string
997
+ if status.to_i == 401
998
+ # When accessing a protected resource, we only want to raise an
999
+ # error for 401 responses.
1000
+ message = 'Authorization failed.'
1001
+ if body.strip.length > 0
1002
+ message += " Server message:\n#{body.strip}"
1003
+ end
1004
+ error = ::Signet::AuthorizationError.new(message, request, response)
1005
+ raise error
1006
+ else
1007
+ return response
1008
+ end
1009
+ end
1010
+ end
1011
+ end
1012
+ end