signet 0.1.0

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