signet 0.2.4 → 0.3.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.
@@ -12,11 +12,17 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ gem 'faraday', '~> 0.7.0'
16
+ require 'faraday'
17
+ require 'faraday/utils'
18
+
15
19
  require 'stringio'
16
20
  require 'addressable/uri'
17
21
  require 'signet'
18
22
  require 'signet/errors'
19
23
  require 'signet/oauth_2'
24
+
25
+ gem 'jwt', '~> 0.1.4'
20
26
  require 'jwt'
21
27
 
22
28
  module Signet
@@ -52,11 +58,6 @@ module Signet
52
58
  # The resource owner's username.
53
59
  # - <code>:password</code> —
54
60
  # The resource owner's password.
55
- # - <code>:assertion_type</code> —
56
- # The format of the assertion as defined by the
57
- # authorization server. The value must be an absolute URI.
58
- # - <code>:assertion</code> —
59
- # The raw assertion value.
60
61
  # - <code>:refresh_token</code> —
61
62
  # The refresh token associated with the access token
62
63
  # to be refreshed.
@@ -64,6 +65,9 @@ module Signet
64
65
  # The current access token for this client.
65
66
  # - <code>:id_token</code> —
66
67
  # The current ID token for this client.
68
+ # - <code>:extension_parameters</code> —
69
+ # When using an extension grant type, this the set of parameters used
70
+ # by that extension.
67
71
  #
68
72
  # @example
69
73
  # client = Signet::OAuth2::Client.new(
@@ -112,11 +116,6 @@ module Signet
112
116
  # The resource owner's username.
113
117
  # - <code>:password</code> —
114
118
  # The resource owner's password.
115
- # - <code>:assertion_type</code> —
116
- # The format of the assertion as defined by the
117
- # authorization server. The value must be an absolute URI.
118
- # - <code>:assertion</code> —
119
- # The raw assertion value.
120
119
  # - <code>:refresh_token</code> —
121
120
  # The refresh token associated with the access token
122
121
  # to be refreshed.
@@ -124,8 +123,9 @@ module Signet
124
123
  # The current access token for this client.
125
124
  # - <code>:id_token</code> —
126
125
  # The current ID token for this client.
127
- # - <code>:expires_in</code> —
128
- # The current access token for this client.
126
+ # - <code>:extension_parameters</code> —
127
+ # When using an extension grant type, this the set of parameters used
128
+ # by that extension.
129
129
  #
130
130
  # @example
131
131
  # client.update!(
@@ -138,10 +138,7 @@ module Signet
138
138
  # @see Signet::OAuth2::Client#update_token!
139
139
  def update!(options={})
140
140
  # Normalize key to String to allow indifferent access.
141
- options = options.inject({}) do |accu, (key, value)|
142
- accu[key.to_s] = value
143
- accu
144
- end
141
+ options = options.inject({}) { |accu, (k, v)| accu[k.to_s] = v; accu }
145
142
  self.authorization_uri = options["authorization_uri"]
146
143
  self.token_credential_uri = options["token_credential_uri"]
147
144
  self.client_id = options["client_id"]
@@ -152,8 +149,7 @@ module Signet
152
149
  self.redirect_uri = options["redirect_uri"]
153
150
  self.username = options["username"]
154
151
  self.password = options["password"]
155
- self.assertion_type = options["assertion_type"]
156
- self.assertion = options["assertion"]
152
+ self.extension_parameters = options["extension_parameters"] || {}
157
153
  self.update_token!(options)
158
154
  return self
159
155
  end
@@ -171,7 +167,7 @@ module Signet
171
167
  # - <code>:id_token</code> —
172
168
  # The current ID token for this client.
173
169
  # - <code>:expires_in</code> —
174
- # The current access token for this client.
170
+ # The time in seconds until access token expiration.
175
171
  # - <code>:issued_at</code> —
176
172
  # The timestamp that the token was issued at.
177
173
  #
@@ -186,10 +182,7 @@ module Signet
186
182
  # @see Signet::OAuth2::Client#update!
187
183
  def update_token!(options={})
188
184
  # Normalize key to String to allow indifferent access.
189
- options = options.inject({}) do |accu, (key, value)|
190
- accu[key.to_s] = value
191
- accu
192
- end
185
+ options = options.inject({}) { |accu, (k, v)| accu[k.to_s] = v; accu }
193
186
 
194
187
  self.access_token = options["access_token"] if options["access_token"]
195
188
  self.expires_in = options["expires_in"] if options["expires_in"]
@@ -233,7 +226,7 @@ module Signet
233
226
  end
234
227
  options[:client_id] ||= self.client_id
235
228
  options[:redirect_uri] ||= self.redirect_uri
236
- unless options[:client_id]
229
+ if !options[:client_id]
237
230
  raise ArgumentError, "Missing required client identifier."
238
231
  end
239
232
  unless options[:redirect_uri]
@@ -348,7 +341,7 @@ module Signet
348
341
  when Array
349
342
  new_scope.each do |scope|
350
343
  if scope.include?(' ')
351
- raise Signet::ParseError,
344
+ raise ArgumentError,
352
345
  "Individual scopes cannot contain the space character."
353
346
  end
354
347
  end
@@ -459,48 +452,29 @@ module Signet
459
452
  end
460
453
 
461
454
  ##
462
- # Returns the assertion type associated with this client.
463
- # Used only by the assertion access grant type.
455
+ # Returns the set of extension parameters used by the client.
456
+ # Used only by extension access grant types.
464
457
  #
465
- # @return [String] The assertion type.
466
- def assertion_type
467
- return @assertion_type
458
+ # @return [Hash] The extension parameters.
459
+ def extension_parameters
460
+ return @extension_parameters ||= {}
468
461
  end
469
462
 
470
463
  ##
471
- # Sets the assertion type associated with this client.
472
- # Used only by the assertion access grant type.
464
+ # Sets extension parameters used by the client.
465
+ # Used only by extension access grant types.
473
466
  #
474
- # @param [String] new_assertion_type
475
- # The password.
476
- def assertion_type=(new_assertion_type)
477
- new_assertion_type = Addressable::URI.parse(new_assertion_type)
478
- if new_assertion_type == nil || new_assertion_type.absolute?
479
- @assertion_type = new_assertion_type
467
+ # @param [Hash] new_extension_parameters
468
+ # The parameters.
469
+ def extension_parameters=(new_extension_parameters)
470
+ if new_extension_parameters.respond_to?(:to_hash)
471
+ @extension_parameters = new_extension_parameters.to_hash
480
472
  else
481
- raise ArgumentError, "Assertion type must be an absolute URI."
473
+ raise TypeError,
474
+ "Expected Hash, got #{new_extension_parameters.class}."
482
475
  end
483
476
  end
484
477
 
485
- ##
486
- # Returns the assertion associated with this client.
487
- # Used only by the assertion access grant type.
488
- #
489
- # @return [String] The assertion.
490
- def assertion
491
- return @assertion
492
- end
493
-
494
- ##
495
- # Sets the assertion associated with this client.
496
- # Used only by the assertion access grant type.
497
- #
498
- # @param [String] new_assertion
499
- # The assertion.
500
- def assertion=(new_assertion)
501
- @assertion = new_assertion
502
- end
503
-
504
478
  ##
505
479
  # Returns the refresh token associated with this client.
506
480
  #
@@ -622,7 +596,7 @@ module Signet
622
596
  # @return [TrueClass, FalseClass]
623
597
  # The expiration state of the access token.
624
598
  def expired?
625
- return self.expires_at == nil || Time.now >= self.expires_at
599
+ return self.expires_at != nil && Time.now >= self.expires_at
626
600
  end
627
601
 
628
602
  ##
@@ -633,18 +607,31 @@ module Signet
633
607
  # @return [String]
634
608
  # The inferred grant type.
635
609
  def grant_type
636
- if self.code && self.redirect_uri
637
- return 'authorization_code'
638
- elsif self.assertion && self.assertion_type
639
- return 'assertion'
640
- elsif self.refresh_token
641
- return 'refresh_token'
642
- elsif self.username && self.password
643
- return 'password'
610
+ if @grant_type
611
+ return @grant_type
644
612
  else
645
- # We don't have sufficient auth information, assume an out-of-band
646
- # authorization arrangement between the client and server.
647
- return 'none'
613
+ if self.code && self.redirect_uri
614
+ 'authorization_code'
615
+ elsif self.refresh_token
616
+ 'refresh_token'
617
+ elsif self.username && self.password
618
+ 'password'
619
+ else
620
+ # We don't have sufficient auth information, assume an out-of-band
621
+ # authorization arrangement between the client and server, or an
622
+ # extension grant type.
623
+ nil
624
+ end
625
+ end
626
+ end
627
+
628
+ def grant_type=(new_grant_type)
629
+ case new_grant_type
630
+ when 'authorization_code', 'refresh_token',
631
+ 'password', 'client_credentials'
632
+ @grant_type = new_grant_type
633
+ else
634
+ @grant_type = Addressable::URI.parse(new_grant_type)
648
635
  end
649
636
  end
650
637
 
@@ -657,7 +644,7 @@ module Signet
657
644
  # The authorization code.
658
645
  #
659
646
  # @return [Array] The request object.
660
- def generate_access_token_request
647
+ def generate_access_token_request(options={})
661
648
  if self.token_credential_uri == nil
662
649
  raise ArgumentError, 'Missing token endpoint URI.'
663
650
  end
@@ -676,9 +663,6 @@ module Signet
676
663
  when 'password'
677
664
  parameters['username'] = self.username
678
665
  parameters['password'] = self.password
679
- when 'assertion'
680
- parameters['assertion_type'] = self.assertion_type
681
- parameters['assertion'] = self.assertion
682
666
  when 'refresh_token'
683
667
  parameters['refresh_token'] = self.refresh_token
684
668
  else
@@ -694,44 +678,32 @@ module Signet
694
678
  ['Cache-Control', 'no-store'],
695
679
  ['Content-Type', 'application/x-www-form-urlencoded']
696
680
  ]
697
- return [
698
- method,
699
- self.token_credential_uri.to_str,
700
- headers,
701
- [Addressable::URI.form_encode(parameters)]
702
- ]
681
+ return Faraday::Request.create(method.to_s.downcase.to_sym) do |req|
682
+ req.url(Addressable::URI.parse(self.token_credential_uri))
683
+ req.headers = Faraday::Utils::Headers.new(headers)
684
+ req.body = Addressable::URI.form_encode(parameters)
685
+ end
703
686
  end
704
687
 
705
688
  def fetch_access_token(options={})
706
- adapter = options[:adapter]
707
- unless adapter
708
- require 'httpadapter'
709
- require 'httpadapter/adapters/net_http'
710
- adapter = HTTPAdapter::NetHTTPAdapter.new
711
- end
712
- connection = options[:connection]
713
- request = self.generate_access_token_request
714
- response = adapter.transmit(request, connection)
715
- status, headers, body = response
716
- merged_body = StringIO.new
717
- body.each do |chunk|
718
- merged_body.write(chunk)
719
- end
720
- body = merged_body.string
721
- if status.to_i == 200
722
- return ::Signet::OAuth2.parse_json_credentials(body)
723
- elsif [400, 401, 403].include?(status.to_i)
689
+ options[:connection] ||= Faraday.default_connection
690
+ request = self.generate_access_token_request(options)
691
+ request_env = request.to_env(options[:connection])
692
+ response = options[:connection].app.call(request_env)
693
+ if response.status.to_i == 200
694
+ return ::Signet::OAuth2.parse_json_credentials(response.body)
695
+ elsif [400, 401, 403].include?(response.status.to_i)
724
696
  message = 'Authorization failed.'
725
- if body.strip.length > 0
726
- message += " Server message:\n#{body.strip}"
697
+ if response.body.to_s.strip.length > 0
698
+ message += " Server message:\n#{response.body.to_s.strip}"
727
699
  end
728
700
  raise ::Signet::AuthorizationError.new(
729
701
  message, :request => request, :response => response
730
702
  )
731
703
  else
732
- message = "Unexpected status code: #{status}."
733
- if body.strip.length > 0
734
- message += " Server message:\n#{body.strip}"
704
+ message = "Unexpected status code: #{response.status}."
705
+ if response.body.to_s.strip.length > 0
706
+ message += " Server message:\n#{response.body.to_s.strip}"
735
707
  end
736
708
  raise ::Signet::AuthorizationError.new(
737
709
  message, :request => request, :response => response
@@ -782,13 +754,21 @@ module Signet
782
754
  }.merge(options)
783
755
  if options[:request]
784
756
  if options[:request].kind_of?(Array)
785
- request = options[:request]
786
- elsif options[:adapter]
787
- request = options[:adapter].adapt_request(options[:request])
757
+ method, uri, headers, body = options[:request]
758
+ elsif options[:request].kind_of?(Faraday::Request)
759
+ unless options[:connection]
760
+ raise ArgumentError,
761
+ "Faraday::Request used, requires a connection to be provided."
762
+ end
763
+ method = options[:request].method.to_s.downcase.to_sym
764
+ uri = options[:connection].build_url(
765
+ options[:request].path, options[:request].params
766
+ )
767
+ headers = options[:request].headers || {}
768
+ body = options[:request].body || ''
788
769
  end
789
- method, uri, headers, body = request
790
770
  else
791
- method = options[:method] || 'GET'
771
+ method = options[:method] || :get
792
772
  uri = options[:uri]
793
773
  headers = options[:headers] || []
794
774
  body = options[:body] || ''
@@ -817,16 +797,20 @@ module Signet
817
797
  if !body.kind_of?(String)
818
798
  raise TypeError, "Expected String, got #{body.class}."
819
799
  end
820
- method = method.to_s.upcase
800
+ method = method.to_s.downcase.to_sym
821
801
  headers << [
822
802
  'Authorization',
823
803
  ::Signet::OAuth2.generate_bearer_authorization_header(
824
804
  self.access_token,
825
- options[:realm] ? ['realm', options[:realm]] : nil
805
+ options[:realm] ? [['realm', options[:realm]]] : nil
826
806
  )
827
807
  ]
828
808
  headers << ['Cache-Control', 'no-store']
829
- return [method, uri.to_str, headers, [body]]
809
+ return Faraday::Request.create(method.to_s.downcase.to_sym) do |req|
810
+ req.url(Addressable::URI.parse(uri))
811
+ req.headers = Faraday::Utils::Headers.new(headers)
812
+ req.body = body
813
+ end
830
814
  end
831
815
 
832
816
  ##
@@ -848,21 +832,15 @@ module Signet
848
832
  # The HTTP body for the request.
849
833
  # - <code>:realm</code> —
850
834
  # The Authorization realm. See RFC 2617.
851
- # - <code>:adapter</code> —
852
- # The HTTP adapter.
853
- # Defaults to <code>HTTPAdapter::NetHTTPAdapter.new</code>.
854
835
  # - <code>:connection</code> —
855
- # An open, manually managed HTTP connection.
856
- # Must be of type <code>HTTPAdapter::Connection</code> and the
857
- # internal connection representation must match the HTTP adapter
858
- # being used.
836
+ # The HTTP connection to use.
837
+ # Must be of type <code>Faraday::Connection</code>.
859
838
  #
860
839
  # @example
861
840
  # # Using Net::HTTP
862
841
  # response = client.fetch_protected_resource(
863
842
  # :uri => 'http://www.example.com/protected/resource'
864
843
  # )
865
- # status, headers, body = response
866
844
  #
867
845
  # @example
868
846
  # # Using Typhoeus
@@ -873,31 +851,19 @@ module Signet
873
851
  # :adapter => HTTPAdapter::TyphoeusAdapter.new,
874
852
  # :connection => connection
875
853
  # )
876
- # status, headers, body = response
877
854
  #
878
855
  # @return [Array] The response object.
879
856
  def fetch_protected_resource(options={})
880
- adapter = options[:adapter]
881
- unless adapter
882
- require 'httpadapter'
883
- require 'httpadapter/adapters/net_http'
884
- adapter = HTTPAdapter::NetHTTPAdapter.new
885
- end
886
- connection = options[:connection]
857
+ options[:connection] ||= Faraday.default_connection
887
858
  request = self.generate_authenticated_request(options)
888
- response = adapter.transmit(request, connection)
889
- status, headers, body = response
890
- merged_body = StringIO.new
891
- body.each do |chunk|
892
- merged_body.write(chunk)
893
- end
894
- body = merged_body.string
895
- if status.to_i == 401
859
+ request_env = request.to_env(options[:connection])
860
+ response = options[:connection].app.call(request_env)
861
+ if response.status.to_i == 401
896
862
  # When accessing a protected resource, we only want to raise an
897
863
  # error for 401 responses.
898
864
  message = 'Authorization failed.'
899
- if body.strip.length > 0
900
- message += " Server message:\n#{body.strip}"
865
+ if response.body.to_s.strip.length > 0
866
+ message += " Server message:\n#{response.body.to_s.strip}"
901
867
  end
902
868
  raise ::Signet::AuthorizationError.new(
903
869
  message, :request => request, :response => response
@@ -17,8 +17,8 @@ unless defined? Signet::VERSION
17
17
  module Signet
18
18
  module VERSION
19
19
  MAJOR = 0
20
- MINOR = 2
21
- TINY = 4
20
+ MINOR = 3
21
+ TINY = 0
22
22
 
23
23
  STRING = [MAJOR, MINOR, TINY].join('.')
24
24
  end
@@ -19,6 +19,9 @@ require 'addressable/uri'
19
19
  require 'stringio'
20
20
 
21
21
  def merge_body(chunked_body)
22
+ if chunked_body == nil
23
+ raise ArgumentError, "Expected chunked body, got nil."
24
+ end
22
25
  merged_body = StringIO.new
23
26
  chunked_body.each do |chunk|
24
27
  merged_body.write(chunk)
@@ -513,6 +516,16 @@ describe Signet::OAuth1::Client, 'configured' do
513
516
  end).should raise_error(ArgumentError)
514
517
  end
515
518
 
519
+ it 'should raise an error if a request is provided without a connection' do
520
+ (lambda do
521
+ @client.generate_authenticated_request(
522
+ :request => Faraday::Request.new(:get) do |req|
523
+ req.url('http://www.example.com/')
524
+ end
525
+ )
526
+ end).should raise_error(ArgumentError)
527
+ end
528
+
516
529
  it 'should raise an error if no URI is provided' do
517
530
  (lambda do
518
531
  @client.generate_authenticated_request(
@@ -529,8 +542,8 @@ describe Signet::OAuth1::Client, 'configured' do
529
542
  :uri => 'https://photos.example.net/photos',
530
543
  :body => ['A chunked body.']
531
544
  )
532
- method, uri, headers, body = request
533
- merge_body(body).should == 'A chunked body.'
545
+ request.should be_kind_of(Faraday::Request)
546
+ request.body.should == 'A chunked body.'
534
547
  end
535
548
 
536
549
  it 'should not raise an error if a request body is chunked' do
@@ -542,8 +555,8 @@ describe Signet::OAuth1::Client, 'configured' do
542
555
  :uri => 'https://photos.example.net/photos',
543
556
  :body => chunked_body
544
557
  )
545
- method, uri, headers, body = request
546
- merge_body(body).should == 'A chunked body.'
558
+ request.should be_kind_of(Faraday::Request)
559
+ request.body.should == 'A chunked body.'
547
560
  end
548
561
 
549
562
  it 'should raise an error if a request body is of a bogus type' do
@@ -560,13 +573,9 @@ describe Signet::OAuth1::Client, 'configured' do
560
573
  # Repeat this because signatures change from test to test
561
574
  10.times do
562
575
  request = @client.generate_temporary_credential_request
563
- method, uri, headers, body = request
564
- method.should == 'POST'
565
- uri.should == 'http://example.com/temporary_credentials'
566
- authorization_header = nil
567
- headers.each do |(header, value)|
568
- authorization_header = value if header == 'Authorization'
569
- end
576
+ request.method.should == :post
577
+ request.path.should === 'http://example.com/temporary_credentials'
578
+ authorization_header = request.headers['Authorization']
570
579
  parameters = ::Signet::OAuth1.parse_authorization_header(
571
580
  authorization_header
572
581
  ).inject({}) { |h,(k,v)| h[k]=v; h }
@@ -589,13 +598,9 @@ describe Signet::OAuth1::Client, 'configured' do
589
598
  request = @client.generate_token_credential_request(
590
599
  :verifier => '473f82d3'
591
600
  )
592
- method, uri, headers, body = request
593
- method.should == 'POST'
594
- uri.should == 'http://example.com/token_credentials'
595
- authorization_header = nil
596
- headers.each do |(header, value)|
597
- authorization_header = value if header == 'Authorization'
598
- end
601
+ request.method.should == :post
602
+ request.path.should === 'http://example.com/token_credentials'
603
+ authorization_header = request.headers['Authorization']
599
604
  parameters = ::Signet::OAuth1.parse_authorization_header(
600
605
  authorization_header
601
606
  ).inject({}) { |h,(k,v)| h[k]=v; h }
@@ -625,15 +630,11 @@ describe Signet::OAuth1::Client, 'configured' do
625
630
  signed_request = @client.generate_authenticated_request(
626
631
  :request => original_request
627
632
  )
628
- method, uri, headers, body = signed_request
629
- method.should == 'GET'
630
- uri.should ==
633
+ signed_request.method.should == :get
634
+ signed_request.path.should ===
631
635
  'https://photos.example.net/photos?file=vacation.jpg&size=original'
632
- authorization_header = nil
633
- headers.each do |(header, value)|
634
- authorization_header = value if header == 'Authorization'
635
- end
636
- merge_body(body).should == ''
636
+ authorization_header = signed_request.headers['Authorization']
637
+ signed_request.body.should == ''
637
638
  parameters = ::Signet::OAuth1.parse_authorization_header(
638
639
  authorization_header
639
640
  ).inject({}) { |h,(k,v)| h[k]=v; h }
@@ -667,15 +668,11 @@ describe Signet::OAuth1::Client, 'configured' do
667
668
  signed_request = @client.generate_authenticated_request(
668
669
  :request => original_request
669
670
  )
670
- method, uri, headers, body = signed_request
671
- method.should == 'POST'
672
- uri.should ==
671
+ signed_request.method.should == :post
672
+ signed_request.path.should ===
673
673
  'https://photos.example.net/photos'
674
- authorization_header = nil
675
- headers.each do |(header, value)|
676
- authorization_header = value if header == 'Authorization'
677
- end
678
- merge_body(body).should == 'file=vacation.jpg&size=original'
674
+ authorization_header = signed_request.headers['Authorization']
675
+ signed_request.body.should == 'file=vacation.jpg&size=original'
679
676
  parameters = ::Signet::OAuth1.parse_authorization_header(
680
677
  authorization_header
681
678
  ).inject({}) { |h,(k,v)| h[k]=v; h }