signet 0.2.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 }